目录
死锁的概念与成因
栗子
死锁的情况
哲学家问题
如何避免死锁
必要条件
死锁的解决方案
总结
死锁的概念与成因
多个线程同时被阻塞,他们中的其中一个或者全部都在等待某个资源的释放,导致线程无限期被阻塞,程序无法停止
栗子
我和美女a出去吃饺子,吃饺子要醋和酱油
我拿起醋他拿起了酱油
我说: 把酱油给我我给你醋
他说: 他醋给我我给你酱油
结果两人争执不下谁也不让谁,就构成死锁,我两就是两个线程
死锁的情况
一线程一把锁
可重入锁不会产生死锁,不可重入锁会发生(之前的文章有介绍过可重入锁)
两个线程两把锁
是可重入锁也会发生死锁
N个线程,M把锁
就更加容易发生死锁了
哲学家问题
这就牵扯到哲学家就餐问题
如图
这五个哲学家,只会进行思考人生和吃饭
思考人生放下筷子,吃饭就会拿起筷子,只有拿起两双筷子吃完才会放下
如果只拿起一只,就会一直拿着不会放开
假设五个哲学家每个人都拿起自己左手边的筷子,那就会永远发生阻塞,产生死锁
如何避免死锁
先了解死锁的四个必要条件
必要条件
1. 互斥使用. 一个线程拿到锁后,另一个线程无法使用
2. 不可抢占. 一个线程拿到锁以后,只能自己主动释放,不能被其他线程抢占
3. 请求与保持. 在资源请求者请求到其他资源的时候,同时保持对原有资源的抢占(吃着碗里的,看着锅里的)
4. 循环等待. 存在一个等待队列 : p1 占用 p2 的资源, p2 占用着 p1 的资源,就形成了一个等待环路
如果上述条件都成立,那就会形成死锁.
但是如果打破其中一条就会使死锁消失
其中最容易破坏的就是循环等待
死锁的解决方案
假设有 n 个线程想获取 m 把锁,我们规定一个所排序,比如(1,2,3,4.........)按照从小到大来进行获取锁,这样可以破坏环路,防止循环等待(可能很难理解,看一段代码就容易理解了)
先看一段代码
public static Object locker1 = new Object();
public static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized(locker1){
synchronized (locker2){
//工作
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized(locker2){
synchronized (locker1){
//工作
}
}
});
t2.start();
}
在这段代码中就会出现循环等待的情况
public static Object locker1 = new Object();
public static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized(locker1){
synchronized (locker2){
//工作
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized(locker1){
synchronized (locker2){
//工作
}
}
});
t2.start();
}
只需要固定加锁的顺序,先获取到 1 才可以获取 2 ,这样就会破坏循环等待的情况
总结
1. 死锁形成的四个条件
2. 破坏循环等待是最常用的方法