且看下面这段代码:
public class ReeantrantAndUnReeantrant {
public static Object locker = new Object();
public static void main(String[] args) {
synchronized (locker) {
synchronized (locker) {
System.out.println("ABC");
}
}
}
}
会不会打印ABC
?
在java中synchronized
是可重入锁。那到底什么是可重入锁?
可重入锁是当一个线程获取一个锁多次,这个线程仍然可以拿到这个锁。
不可重入锁跟可重入锁相反,一个线程拿到一个锁后,后面再尝试拿锁是拿不了的。
在java
中sychronized
是可重入锁,所以上面的代码能跑起来,但在其他语言中就不一定了。
如果synchronized
不是可重入锁,上面的代码就会出问题,会出现死锁。
由于java
中synchronized
是可重入锁,所以没有需要解决的问题。
两个线程两把锁,就算是可重入锁也会导致死锁。
如下图:
t1
拿了locker1
,t2
拿了locker2
。
t1
尝试获取locker2
,t2
尝试获取locker1
。
这就会导致t1
一直获取不到locker2
,t2
获取不到locker1
。
t1
和t2
就这么僵着,谁也拿不到谁的锁。
代码如下:
public class TwoThreadTwoLock {
public static Object locker1 = new Object();
public static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (locker1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2) {
System.out.println("t1");
}
}
});
Thread t2 = new Thread(()->{
synchronized (locker2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1) {
System.out.println("t2");
}
}
});
t1.start();
t2.start();
}
}
规定获取锁的顺序,比如规定每个线程都先获取locker1
再获取locker2
。
synchronized(locker1) {
synchronized(locker2) {
...
}
}
对上面的代码修改后:
public class TwoThreadTwoLock {
public static Object locker1 = new Object();
public static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (locker1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2) {
System.out.println("t1");
}
}
});
Thread t2 = new Thread(()->{
synchronized (locker1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2) {
System.out.println("t2");
}
}
});
t1.start();
t2.start();
}
}
如下图:
每个线程依赖两个锁,只有拿到这两个锁,线程才能执行代码。
t1依赖locker1
和locker2
~~~~ t2依赖locker1
和locker5
t3依赖locker2
和locker3
~~~~ t4依赖locker4
和locker5
t5依赖locker3
和locker4
同时拿起了一把锁:
现在每个线程都有了一把锁,但是还需要另外一把,所以各个线程又去获取第二把锁。
现在每个线程又陷入了一个尴尬的局面,每个线程都要去获取另一把锁,但是每个线程又获取不到。
跟两个线程两个锁出现死锁一样,还是要约定获取锁的顺序,
把锁编号,规定先从小的锁开始获取还是从大的锁开始获取。
举个例子:
我们规定从编号小的锁开始获取锁。
t1依赖locker1
和locker2
,所以先获取locker1
再获取locker2
synchronized(locker1) {
synchronized(locker2) {
}
}
t2依赖locker1
和locker5
,所以先获取locker1
再获取locker5
synchronized(locker1) {
synchronized(locker5) {
}
}
拿锁后:
第二次拿锁:
t4拿到了两个锁:
t4代码执行完毕,释放锁:
各个线程继续拿锁,以此往复:
最后各个线程都可以执行完毕。