死锁的原因和解决方案

文章详细阐述了死锁产生的四个必要条件:互斥使用、不可抢占、请求和保持、循环等待,并通过Java代码示例解释了不同线程和锁组合下的死锁情况。为了避免死锁,提出了破坏循环等待的策略,即规定加锁顺序,确保所有线程按照同一顺序获取锁。
摘要由CSDN通过智能技术生成

死锁产生的四个必要条件:

    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) {
                
            }
        }

                   

                                                   以上就是对死锁过程详解的总结,点赞关注不迷路!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良月初十♧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值