【JavaEE】死锁的成因和解决方案

文章目录
- 1.可重入锁和不可重入锁
-
- 1.1可重入锁
- 1.2不可重入锁
- 1.3解决方案
- 2.两个线程两把锁
-
- 2.1问题
- 2.2解决方案
- 3.多个线程多把锁
-
- 3.1问题
- 3.2解决方案
1.可重入锁和不可重入锁
且看下面这段代码:
public class ReeantrantAndUnReeantrant {public static Object locker = new Object();public static void main(String[] args) {synchronized (locker) {synchronized (locker) {System.out.println("ABC");}}}
}
会不会打印ABC?
执行结果:

结果打印了ABC,为什么呢?😕
在java中synchronized是可重入锁。那到底什么是可重入锁?
1.1可重入锁
可重入锁是当一个线程获取一个锁多次,这个线程仍然可以拿到这个锁。


1.2不可重入锁
不可重入锁跟可重入锁相反,一个线程拿到一个锁后,后面再尝试拿锁是拿不了的。


在java中sychronized是可重入锁,所以上面的代码能跑起来,但在其他语言中就不一定了。
如果synchronized不是可重入锁,上面的代码就会出问题,会出现死锁。
1.3解决方案
由于java中synchronized是可重入锁,所以没有需要解决的问题。
2.两个线程两把锁
2.1问题
两个线程两把锁,就算是可重入锁也会导致死锁。
如下图:
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();}
}
运行结果:

2.2解决方案
规定获取锁的顺序,比如规定每个线程都先获取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();}
}
执行结果:

3.多个线程多把锁
3.1问题
如下图:
每个线程依赖两个锁,只有拿到这两个锁,线程才能执行代码。
t1依赖locker1和locker2~~~~ t2依赖locker1和locker5
t3依赖locker2和locker3~~~~ t4依赖locker4和locker5
t5依赖locker3和locker4

同时拿起了一把锁:


现在每个线程都有了一把锁,但是还需要另外一把,所以各个线程又去获取第二把锁。

现在每个线程又陷入了一个尴尬的局面,每个线程都要去获取另一把锁,但是每个线程又获取不到。
- 可以看到多个线程和多个锁之间也是会出现死锁的。
3.2解决方案
跟两个线程两个锁出现死锁一样,还是要约定获取锁的顺序,
把锁编号,规定先从小的锁开始获取还是从大的锁开始获取。
举个例子:
我们规定从编号小的锁开始获取锁。
t1依赖locker1和locker2,所以先获取locker1再获取locker2
synchronized(locker1) {synchronized(locker2) {}
}
t2依赖locker1和locker5,所以先获取locker1再获取locker5
synchronized(locker1) {synchronized(locker5) {}
}
拿上面的例子来说,规定先获取小的锁再获取大的锁。

拿锁后:

第二次拿锁:
t4拿到了两个锁:

t4代码执行完毕,释放锁:

各个线程继续拿锁,以此往复:

最后各个线程都可以执行完毕。


