【多线程初阶】死锁的产生 & 如何避免死锁

06-01 1659阅读

文章目录

  • 关于死锁
    • 一.死锁的三种情况
      • 1.一个线程,一把锁,连续多次加锁
      • 2.两个线程,两把锁
      • 3.N个线程,M把锁 --哲学家就餐问题
      • 二.如何避免死锁
        • 死锁是如何构成的(四个必要条件)
        • 打破死锁
        • 三.死锁小结

          关于死锁

          一.死锁的三种情况

          【多线程初阶】死锁的产生 & 如何避免死锁

          • 1.一个线程,一把锁,连续多次加锁 -->由synchronized 锁解决
          • 2.两个线程,两把锁,每个线程获取到一把锁之后,尝试获取对方的锁
          • 3.N个线程,M把锁 -->一个经典的模型,哲学家就餐问题

            1.一个线程,一把锁,连续多次加锁

            【多线程初阶】死锁的产生 & 如何避免死锁

            一个线程,一把锁,连续多次加锁,在实际学习和工作中,是很容易被写出来的,一旦方法调用的层次比较深,就搞不好容易出现这样的情况,想要解除阻塞,需要 往下执行,想要往下执行,就需要等待第一次的锁被释放,这样就形成了死锁(dead lock),就如同下面的Demo18,一个线程对同一把锁进行多次加锁,但是运行出来结果没错

            为了解决当方法调用层次比较深出现一个线程,一把锁,多次加锁形成死锁的情况,Java中的synchronized 就引入了可重入概念,在上一篇博客 synchronized关键字里有详细解释,本篇博客不再赘述

            代码示例:

            class Counter{
                private int count = 0;
                synchronized public void add(){
                    count++;
                }
                public int get(){
                    return count;
                }
                public synchronized  static void func(){
                    synchronized (Counter.class){
                    }
                }
            }
            public class Demo18 {
                public static void main(String[] args) throws InterruptedException {
                    Object locker = new Object();
                    Counter counter = new Counter();
                    Thread t1 = new Thread(() ->{
                        for (int i = 0; i {
                        for (int i = 0; i  
            

            【多线程初阶】死锁的产生 & 如何避免死锁

            2.两个线程,两把锁

            两个线程,两把锁,每个线程获取到一把锁之后,尝试获取对方的锁

            用生活中的实际场景,举例说明:

            比如,吃饺子~~,朝新喜欢蘸酱油吃,小舟喜欢蘸醋吃,后来两人都习惯了对方的习惯,两人都是同时蘸醋和酱油吃饺子,朝新拿起酱油,小舟拿起醋

            朝新说:你把醋给我,我用完了,全都给你

            小舟说:不行,你把酱油先给我,我用完了,全都给你

            此时两个线程互不相让,就会构成死锁~~

            还比如,房钥匙锁车里了,车钥匙锁家里了

            代码示例:

            public class Demo20 {
                public static void main(String[] args) throws InterruptedException {
                    Object lock1 = new Object();
                    Object lock2 = new Object();
                    Thread t1 =new Thread(() ->{
                        synchronized (lock1){
                            //朝新拿起酱油
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //朝新尝试拿起醋
                            synchronized (lock2){
                                System.out.println("t1 线程两个锁都获取到");
                            }
                        }
                    });
                    Thread t2 =new Thread(() ->{
                        synchronized (lock2){
                            //小舟拿起醋
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //小舟尝试拿起酱油
                            synchronized (lock1){
                                System.out.println("t2 线程两个锁都获取到");
                            }
                        }
                    });
                    t1.start();
                    t2.start();
                    t1.join();
                    t2.join();
                }
            }
            

            【多线程初阶】死锁的产生 & 如何避免死锁

            必须是,拿到第一把锁,再拿第二把锁(不能释放第一把锁)

            【多线程初阶】死锁的产生 & 如何避免死锁

            其中加入sleep的作用:

            【多线程初阶】死锁的产生 & 如何避免死锁

            加入sleep就是为了确保上述错误代码构成死锁

            如果让我们手写一个出现死锁的代码,就是要通过上述代码,写两个线程两把锁,注意要精确控制好加锁的顺序,不进行控制的话,随机调度就有可能不构成死锁了

            3.N个线程,M把锁 --哲学家就餐问题

            【多线程初阶】死锁的产生 & 如何避免死锁

            大部分情况下,上述模型,可以很好的运转,但是在一些极端情况下会造成死锁

            像是,同一时刻,大家都想吃面条,同时拿起左手的筷子,此时任何一个线程都无法拿起右手的筷子,任何一个哲学家都吃不成面条

            每个线程,都不会放下手里的筷子,而是阻塞等待,构成死锁

            上述场景虽说非常极端,但是在以后的学习和工作中,比如我们以后会做服务器开发,同时为很多个用户提供服务,假设上述场景,即使出现死锁的概率是1%%,服务器可能一天要处理几千万的请求(比如百度,一天要处理10亿量级的请求),这样就会出现10万次死锁情况,就比如温总理说的:在咱们国家,再小的问题,乘以13亿都是大问题~~,那么如何避免死锁问题呢?

            二.如何避免死锁

            死锁是如何构成的(四个必要条件)

            【多线程初阶】死锁的产生 & 如何避免死锁

            • 1.锁是互斥的,一个线程拿到锁之后,另一个线程再尝试获取锁,必须要阻塞等待 (锁的基本性质)
            • 2.锁是不可抢占的(不可剥夺),线程1拿到锁 线程2也尝试获取这个锁,线程2必须阻塞等待,而不是线程2直接把锁抢过来 (锁的基本特性)
            • 3.请求和保持,一个线程拿到锁1 之后不释放锁1的前提下,去获取锁2
            • 4.循环等待,多个线程,多把锁之间的等待过程,构成了"循环",A等待B,B等待C,C等待A

              以上四个形成死锁的必要条件,其中1和2都是锁自己的基本性质和特性,至少,Java中的synchronized锁是遵守这两点的,各种语言中内置的锁/主流的锁,都是遵守这两点的,这两点我们改变不了

              只要破坏上述的3 ,4任何一个条件都能够打破死锁

              打破死锁

              • 1.打破必要条件3 :请求和保持

                如果是先放下左手的筷子,再去拿右手的筷子,就不会构成死锁了,也就是代码中加锁的时候,不要"嵌套加锁"

                代码示例:

                public class Demo20 {
                    public static void main(String[] args) throws InterruptedException {
                        Object lock1 = new Object();
                        Object lock2 = new Object();
                        Thread t1 =new Thread(() ->{
                            synchronized (lock1){
                                //朝新拿起酱油
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            //朝新尝试拿起醋
                            synchronized (lock2){
                                System.out.println("t1 线程两个锁都获取到");
                            }
                        });
                        Thread t2 =new Thread(() ->{
                            synchronized (lock2){
                                //小舟拿起醋
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            //小舟尝试拿起酱油
                            synchronized (lock1){
                                System.out.println("t2 线程两个锁都获取到");
                            }
                        });
                        t1.start();
                        t2.start();
                        t1.join();
                        t2.join();
                    }
                }
                

                【多线程初阶】死锁的产生 & 如何避免死锁

                这种破坏死锁的方法不够通用,有些情况下,确实需要拿到多个锁,再进行某个操作的(嵌套,很难避免)

                • 2.打破必要条件4 :循环等待

                  约定好加锁的顺序,就可以破除循环等待了,我们约定好,每个线程加锁的时候,永远是先获取序号小的锁,后获取序号大的锁

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  通过上述哲学家就餐模型,我们可以观察到,只要规定好加锁的顺序,就可以打破循环等待,从而避免死锁问题

                  我们使用上述吃饺子过程中出现的死锁问题来观察,通过破除循环等待,也就是规定好加锁顺序后,是如何避免死锁问题的

                  public class Demo20 {
                      public static void main(String[] args) throws InterruptedException {
                          Object lock1 = new Object();
                          Object lock2 = new Object();
                          Thread t1 =new Thread(() ->{
                              synchronized (lock1){
                                  //朝新拿起酱油
                                  System.out.println("t1 拿到locker1");
                                  try {
                                      Thread.sleep(1000);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  //朝新尝试拿起醋
                                  synchronized (lock2){
                                      System.out.println("t1 线程两个锁都获取到 吃面条");
                                  }
                              }
                          });
                          Thread t2 =new Thread(() ->{
                              synchronized (lock1){
                                  //小舟拿起醋
                                  System.out.println("t2 拿到locker1");
                                  try {
                                      Thread.sleep(1000);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  //小舟尝试拿起酱油
                                  synchronized (lock2){
                                      System.out.println("t2 线程两个锁都获取到 吃面条");
                                  }
                              }
                          });
                          t1.start();
                          t2.start();
                          t1.join();
                          t2.join();
                      }
                  }
                  

                  【多线程初阶】死锁的产生 & 如何避免死锁

                  三.死锁小结

                  【多线程初阶】死锁的产生 & 如何避免死锁

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码