死锁产生的四个必要条件:
1.互斥使用:也就是说一个线程如果对这个锁对象进行了加锁,另一个线程也要对这个锁对象进行加锁,就要进行阻塞等待。(锁的基本特点)
2.不可抢占:不能去抢锁,一个线程拿到锁之后,只能是自己执行完了然后释放锁,其他线程不能强行占有。(锁的基本特点)
3.请求和保持:同时拿到了这把锁,然后还要对别的锁对象进行加锁(M个线程,N把锁,和代码的特点有关)
4.循环等待:逻辑是依赖循环的。(家门钥匙锁车里了,车钥匙锁家里了(也和代码的特点有关))
注:既然是必要条件,所以缺一不可(少一个条件都不能构成死锁)。
介绍死锁的几种情况:
1. 一个线程,一把锁。 如下图代码:首先synchronized是针对put方法加锁,如果同时啊hi要对里面的size方法加锁,此时就会产生死锁(如果是可重入锁没事(synchronized属于可重入锁),对于不可重入锁就会死锁)。此时就是对一个线程加锁两次的情况,第二次尝试加锁,需要等待第一个锁释放,第一个锁释放,要等待第二个锁加锁成功。逻辑上是矛盾的,就会进入死锁状态。(但是Java中的synchronized锁机制是可重入锁,他会判定当前的线程是否已经拥有这个锁对象了,如果已经拥有了,第二次加锁时会直接放行)
class BlockingQueue {
synchronized void put(int elem) {
this.size();......
}
synchronized int size() {
......
}
}
2. 两个线程,两把锁,此时即使是可重入锁,也会产生死锁情况,如下图代码,就会产生死锁,有两个线程 t1 和 t2 。t1对locker1加锁,t2对locker2加锁,之后,t2要对locker1加锁,就需要等t1释放locker1,同样的,t1尝试对locker1再加锁,就需要等t2释放locker1才能继续加锁。 可以类比那个两个人吃饺子,同时要酱油和醋,一个人拿到了酱油,另一个人拿到了醋,一个人说你把醋给我我用完了给你,另一个人说你把酱油给我,我用完了给你。此时两个人僵持不下,然后就进入死锁状态了(两个人就是两个线程酱油和醋就是两把锁)。
public class ThreadDemo31 {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
synchronized (locker1) {
synchronized (locker2) {
}
}
synchronized (locker2) {
synchronized (locker1) {
}
}
}
}
3.N个线程,M把锁,这种情况多个线程多把锁就更容易死锁了,例如哲学家就餐问题:4个哲学家会思考哲学和吃面条,如下图:桌子上只有四根筷子,哲学家堆积的吃面条(拿起筷子)和思考哲学(放下筷子),如果四个哲学家同时拿起左手边的筷子(谁也吃不到面条,因为一人只有一根筷子),此时就进入死锁状态了,筷子就是对应的锁对象,哲学家对应的就是多个线程。也就是说这个场景是有多个线程,每个线程要加两次锁,如果此时已经加了一个锁,另一个被抢占了,此时线程就会进入阻塞等待一直等获取到另一把锁,同时会占用着已经加了的这把锁。
死锁在我们日常开发写代码时是一个非常容易产生的bug并且是一个严重的bug,那如何避免死锁的产生呢?
简单有效容易理解的办法就是破坏循环等待,(破坏了一个条件,那么死锁也就不成立了):
针对锁进行编号,如果同时获取多把锁,就约定加锁顺序,一定是先按照编号小的先进行加锁,然后再对编号大的进行加锁。(或者先对大的编号进行加锁,再对小的编号进行加锁,只要多个线程加锁的顺序是一样的即可)。
例如还是哲学家就餐问题:如下图,对筷子进行编号,一号哲学家先获取1号筷子,二号获取2号筷子,三号获取3号筷子,注意,四号是获取四号筷子吗?? 不是,显然1号筷子编号是更小的。但是1号筷子已经有人获取了,所以此时四号哲学家是获取不到筷子的,所以死锁就不能构成,四号就进行阻塞等待去获取1号筷子,此刻,3号就获取到了4号筷子,就可以吃面条了,吃完就放下,此时二号哲学家就可以拿到3了,然后吃,然后放下筷子,在轮到一号哲学家,吃,放下筷子,四号最后吃........此时死锁的问题就解决了。(只要这个加锁是有顺序的,就不能构成死锁)
再来看两个线程两把锁的情况:
如果此时加锁的顺序如下图代码,两个线程t1和t2。t1先加锁locker1,此时t2尝试对locker1加锁时就会进入阻塞等待,等t1释放locker1之后,t2开始对locker1加锁,同时t1对locker2加锁,释放锁之后,t2就也可以对locker2进行加锁了。此时死锁的情况也解决了。
Object locker1 = new Object();
Object locker2 = new Object();
synchronized (locker1) {
synchronized (locker2) {
}
}
synchronized (locker1) {
synchronized (locker2) {
}
}