共计 2475 个字符,预计需要花费 7 分钟才能阅读完成。
死锁(Dead Lock)指的是两个或两个以上的运算单元(过程、线程或协程),都在期待对方开释资源,但没有一方提起开释资源,从而造成了一种阻塞的景象就称为死锁。
比方线程 1 领有了锁 A 的状况下试图获取锁 B,而线程 2 又在领有了锁 B 的状况下试图获取锁 A,这样单方就进入互相阻塞期待的状况,如下图所示:
死锁的代码实现如下:
import java.util.concurrent.TimeUnit;
public class DeadLockTest {public static void main(String[] args) {Object lockA = new Object();
Object lockB = new Object();
// 创立线程 1
Thread t1 = new Thread(() -> {
// 1. 占有锁 A
synchronized (lockA) {System.out.println("线程 1:取得锁 A。");
// 休眠 1s(让线程 2 有工夫先占有锁 B)try {TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
// 2. 获取线程 2 的锁 B
synchronized (lockB) {System.out.println("线程 1:取得锁 B。");
}
}
});
t1.start();
// 创立线程 2
Thread t2 = new Thread(() -> {
// 1. 占有锁 B
synchronized (lockB) {System.out.println("线程 2:取得锁 B。");
// 休眠 1s(保障线程 1 能有短缺的工夫失去锁 A)try {TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
// 2. 获取线程 1 的锁 A
synchronized (lockA) {System.out.println("线程 2:取得锁 A。");
}
}
});
t2.start();}
}
以上程序的执行后果如下图所示:
从上述后果能够看出,线程 1 和线程 2 都在期待对方开释锁,这样就造成了死锁问题。
死锁产生起因
死锁的产生须要满足以下 4 个条件:
- 互斥条件 :指运算单元(过程、线程或协程)对所调配到的资源具备排它性,也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
- 申请和放弃条件 :指运算单元曾经放弃至多一个资源,但又提出了新的资源申请,而该资源已被其它运算单元占有,此时申请运算单元阻塞,但又对本人已取得的其它资源放弃不放。
- 不可剥夺条件 :指运算单元已取得的资源,在未应用完之前,不能被剥夺。
- 环路期待条件 :指在产生死锁时,必然存在运算单元和资源的环形链,即运算单元正在期待另一个运算单元占用的资源,而对方又在期待本人占用的资源,从而造成环路期待的状况。
只有以上 4 个条件同时满足,才会造成死锁。
解决死锁
死锁产生要满足以上 4 个必要条件,那么咱们只须要扭转其中的 1 个或多个条件就能够解决死锁的问题了,比方咱们能够通过批改获取锁的程序来扭转环路期待条件。
在未修改获取锁的程序前,程序的执行流程是这样的:
其中 ① 示意先执行,② 示意后执行。
而扭转锁的获取程序之后的执行流程是这样的:
此时线程 1 和线程 2 获取锁的程序是统一的,都是先获取锁 A,再获取锁 B,此时它们的执行流程如下:
- 线程 1 先获取到锁 A;
- 线程 1 获取到锁 B;
- 线程 1 开释了锁 B;
- 线程 1 开释了锁 A;
- 线程 2 获取到了锁 A;
- 线程 2 获取到了锁 B;
- 线程 2 开释了锁 B;
- 线程 2 开释了锁 A。
对应的实现代码如下:
import java.util.concurrent.TimeUnit;
class DeadLockTest {public static void main(String[] args) {Object lockA = new Object();
Object lockB = new Object();
// 创立线程 1
Thread t1 = new Thread(() -> {
// 1. 获取锁 A
synchronized (lockA) {System.out.println("线程 1:取得锁 A。");
// 休眠 1s
try {TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
// 2. 获取锁 B
synchronized (lockB) {System.out.println("线程 1:取得锁 B。");
System.out.println("线程 1:开释锁 B。");
}
System.out.println("线程 1:开释锁 A。");
}
});
t1.start();
// 创立线程 2
Thread t2 = new Thread(() -> {
try {TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
// 1. 获取锁 A
synchronized (lockA) {System.out.println("线程 2:取得锁 A。");
// 2. 获取锁
synchronized (lockB) {System.out.println("线程 2:取得锁 B。");
System.out.println("线程 2:开释锁 B。");
}
System.out.println("线程 2:开释锁 A。");
}
});
t2.start();}
}
以上程序的执行后果如下图所示:
总结
死锁(Dead Lock)指的是两个或两个以上的运算单元(过程、线程或协程),都在期待对方开释资源,但没有一方提前开释资源,从而造成了一种阻塞的景象就称为死锁。 产生死锁须要同时满足 4 个条件:互斥条件、申请和放弃条件、不可剥夺条件、环路期待条件,因而咱们只须要毁坏其中 1 个或多个条件就能够解决死锁的问题了 。
是非审之于己,毁誉听之于人,得失安之于数。
公众号:Java 面试真题解析
面试合集:https://gitee.com/mydb/interview