并发编程[6]_死锁
1. 死锁的概念
简单的说,死锁是指多个线程在竞争资源时,造成了互相等待阻塞的现象。
例1:
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Object objA = new Object();
Object objB = new Object();
// 线程1 占有 objA 的同时还想访问 objB
Thread t1 = new Thread(() -> {
synchronized (objA){
log.debug("t1现在可以访问objA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1想访问objB");
synchronized (objB){
log.debug("t1现在可以访问objB");
}
}
}, "t1");
// 线程1 占有 objB 的同时还想访问 objA
Thread t2 = new Thread(() -> {
synchronized (objB){
log.debug("t2现在可以访问objB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2想访问objA");
synchronized (objA){
log.debug("t2现在可以访问objA");
}
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(2000);
log.debug("t1:{}",t1.getState());
log.debug("t2:{}",t2.getState());
}
}
运行结果:
2021-04-27 22:22:21.785 [t1] - t1现在可以访问objA
2021-04-27 22:22:21.785 [t2] - t2现在可以访问objB
2021-04-27 22:22:22.787 [t2] - t2想访问objA
2021-04-27 22:22:22.787 [t1] - t1想访问objB
2021-04-27 22:22:23.787 [main] - t1:BLOCKED
2021-04-27 22:22:23.788 [main] - t2:BLOCKED
程序并没有结束。t1占有了objA,在未释放锁的情况,想访问objB,t2也是类似的情况,这样互相循环等待,造成了死锁。
2. 产生死锁的四个条件
(1) 互斥:一个资源一次只能被一个资源使用
(2) 请求与保持:又叫占有并等待。进程中在等待分配其他资源时,对已经占有的资源保持不放。
(3) 不可剥夺:进程获得了资源,在未使用完之前,不得强行抢占。
(4) 循环等待:多个进程之间形成了首尾相接的循环等待关系。每个进程都占有着下一个进程所需要的资源。
上述四个条件若有一个不满足,则不会死锁。所以可以通过破坏上述任一条件来预防死锁。
3. java工具检测死锁
我们可以使用JDK自带的可视化监测工具来检测例1的死锁。
检测出了死锁的两个线程。
4. 处理死锁的四个方法
(1) 预防死锁。预防死锁的原理是破坏死锁的四个必要条件中的一个或多个来进行预防。如资源一次性分配(破坏请求和保持条件),系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源(破坏循环等待条件)等。
(2) 避免死锁。不预先采取各种措施去破坏死锁的四个条件,而是在资源的动态分配过程中,采取手段去防止死锁。
(3) 检测死锁。构建图模型,转化为有向图的环检测问题。
(4) 解除死锁。当检测到死锁时,挂起某些进程,回收资源,再将资源进行分配,可以时阻塞的进程转为就绪状态,继续运行。