线程死锁问题


线程死锁问题

什么是死锁?

  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
  2. 出现死锁后,不会出现异常,不会出现提示,只是所用线程都处于阻塞状态,无法继续

演示死锁问题

接下来我们演示死锁问题

public class ThreadTest {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(()->{
            synchronized (s1){
                s1.append("a");
                s2.append("1");
                synchronized (s2){
                    s1.append("b");
                    s2.append("2");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();

        new Thread(()->{
            synchronized (s2){
                s1.append("c");
                s2.append("3");
                synchronized (s1){
                    s1.append("d");
                    s2.append("4");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();
    }
}

上述代码可能导致死锁,为了提高出现死锁的概率,我们在线程拿到第一个锁后睡一下

public class ThreadTest {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(()->{
            synchronized (s1){
                s1.append("a");
                s2.append("1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s2){
                    s1.append("b");
                    s2.append("2");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();

        new Thread(()->{
            synchronized (s2){
                s1.append("c");
                s2.append("3");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s1){
                    s1.append("d");
                    s2.append("4");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();
    }
}

​ 当第一个线程拿到锁s1时,第二个线程进入方法拿到锁s2,线程1sleep后因为s2被线程2占用,而线程2想要拿s1锁被线1占用,双方都想拿到对方的锁,而自己又无法释放自己的锁,从而造成无法向下执行,进而造成死锁

上面的例子符合产生死锁的四个必要条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

如何预防和避免线程死锁?

如何预防死锁? 破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件 :一次性申请所有的资源。
  2. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3…..Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称<P1、P2、P3…..Pn>序列为安全序列。

我们把线程2的代码改成这样就不会产生死锁了

new Thread(()->{
           synchronized (s1){
               s1.append("c");
               s2.append("3");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (s2){
                   s1.append("d");
                   s2.append("4");
                   System.out.println(s1);
                   System.out.println(s2);
               }
           }
       }).start();
   }

我们分析一下上面的代码为什么避免了死锁的发生?

线程 1 首先获得到 s1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 s2 的监视器锁,可以获取到。然后线程 1 释放了对 s1、s2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。


Author: qwq小小舒
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source qwq小小舒 !
  TOC