多线程与高并发学习一
线程的三个方法sleep,yield,join
sleep让线程进入timed_waiting状态,yield让线程进入Runable中的Ready状态等待获取CPU资源,join是当前线程调用另一线程的join方法,此时CPU调用另一线程等待另一个线程执行完毕后再执行当前的线程。
public class Sleep_Yield_Join {
public static void main(String[] args) {
// testSleep();
// testYield();
testJoin();
}
static void testSleep() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void testYield() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
if (i % 10 == 0) Thread.yield();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("------------B" + i);
if (i % 10 == 0) Thread.yield();
}
}).start();
}
static void testJoin() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
锁的问题验证
如下模拟一个银行账户,分别是设置账户信息以及查询有多少钱,然后对于设置账户信息加上了synchronized锁信息,然后再写一个main函数模拟设置账户信息以及读取账户信息。
public class Account {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(() -> a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
得到如下结果:
因为在第一次getBalance方法的过程中,前面的set()方法执行的过程中线程处于睡眠状态,然后读取的时候调用的是getBalance方法,然后读取的过程中发现此方法是不需要加锁的,所以getBalance方法可以执行,然后输出0.0,这就是脏读,因为sychronized方法和非sychronize的方法是可以同时运行的。
锁的可重入属性
一个同步方法调用另一个同步方法,
public class ReentrantSync {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
new ReentrantSync().m1();
}
}
sychronized是不是可重入的?为什么?
比如父类有一个m方法,然后有一个子类重写了m方法,子类的m方法里面调用了父类的m方法super.m(),如果父子类的m方法都被sychronized修饰了,如果不可重入,那么父子之间的继承关系就被死锁了,这种肯定是不行的。
看如下DEMO:
public class SyncAndOverWrite {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
static class TT extends SyncAndOverWrite {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
}
输出结果如下:
异常跟锁
程序在执行过程中,如果出现异常,默认情况下锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况,比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
因此要非常小心的处理同步业务逻辑中的异常。
public class SyncAndExceptions {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1 / 0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
System.out.println(i);
}
}
}
public static void main(String[] args) {
T12_SyncAndExceptions t = new T12_SyncAndExceptions();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
打印结果如下所示,本来我的业务是只想让t1线程运行的,但是由于异常的抛出让t1释放了锁,导致t2线程运行起来了
synchronized底层实现
JDK早期的实现是重量级的,都是要找操作系统去申请锁
后来的改进:
锁升级的概念:
sync(Object)
首先通过object 的markword记录这个线程ID (偏向锁)
如果有线程争用:此时升级为自旋锁,默认自旋10此,如果十次以后还得不到锁。
则升级位重量级锁 -OS
自旋锁占用CPU资源的,而重量级锁是不占用CPU资源的,直接把竞争的线程放进等待队列里,等待CPU需要调用的时候再通过操作系统唤醒线程。执行时间长且线程数比较多的用重量级锁;加锁代码执行时间特别短,且线程数比较少用自旋锁。
synchronized锁对象不能用String常量,Integer,Long,如果里面定义的字符串常量被其他模块或者其他lib包调用了,然后因为有字符串常量池就会导致其他地方也是用的同一个字符串常量,然后其他地方也恰巧用了锁机制,就会导致别人的代码和你的代码都锁定的是同一个对象,如果是不同的线程的话可能会导致死锁