乐趣区

关于java:面试突击47死锁产生的原因有哪些

死锁(Dead Lock)指的是两个或两个以上的运算单元(过程、线程或协程),都在期待对方开释资源,但没有一方提起开释资源,从而造成了一种阻塞的景象就称为死锁。

比方线程 1 领有了锁 A 的状况下试图获取锁 B,而线程 2 又在领有了锁 B 的状况下试图获取锁 A,这样单方就进入互相阻塞期待的状况,如下图所示:

死锁的代码实现如下:

import java.util.concurrent.TimeUnit;

public class DeadLockTest {public static void main(String[] args) {Object lockA = new Object();
        Object lockB = new Object();
        // 创立线程 1
        Thread t1 = new Thread(() -> {
            // 1. 占有锁 A
            synchronized (lockA) {System.out.println("线程 1:取得锁 A。");
                // 休眠 1s(让线程 2 有工夫先占有锁 B)try {TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                // 2. 获取线程 2 的锁 B
                synchronized (lockB) {System.out.println("线程 1:取得锁 B。");
                }
            }
        });
        t1.start();
        // 创立线程 2
        Thread t2 = new Thread(() -> {
            // 1. 占有锁 B
            synchronized (lockB) {System.out.println("线程 2:取得锁 B。");
                // 休眠 1s(保障线程 1 能有短缺的工夫失去锁 A)try {TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                // 2. 获取线程 1 的锁 A
                synchronized (lockA) {System.out.println("线程 2:取得锁 A。");
                }
            }
        });
        t2.start();}
}

以上程序的执行后果如下图所示:

从上述后果能够看出,线程 1 和线程 2 都在期待对方开释锁,这样就造成了死锁问题。

死锁产生起因

死锁的产生须要满足以下 4 个条件:

  1. 互斥条件 :指运算单元(过程、线程或协程)对所调配到的资源具备排它性,也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
  2. 申请和放弃条件 :指运算单元曾经放弃至多一个资源,但又提出了新的资源申请,而该资源已被其它运算单元占有,此时申请运算单元阻塞,但又对本人已取得的其它资源放弃不放。
  3. 不可剥夺条件 :指运算单元已取得的资源,在未应用完之前,不能被剥夺。
  4. 环路期待条件 :指在产生死锁时,必然存在运算单元和资源的环形链,即运算单元正在期待另一个运算单元占用的资源,而对方又在期待本人占用的资源,从而造成环路期待的状况。

只有以上 4 个条件同时满足,才会造成死锁。

解决死锁

死锁产生要满足以上 4 个必要条件,那么咱们只须要扭转其中的 1 个或多个条件就能够解决死锁的问题了,比方咱们能够通过批改获取锁的程序来扭转环路期待条件。

在未修改获取锁的程序前,程序的执行流程是这样的:

其中 ① 示意先执行,② 示意后执行。
而扭转锁的获取程序之后的执行流程是这样的:

此时线程 1 和线程 2 获取锁的程序是统一的,都是先获取锁 A,再获取锁 B,此时它们的执行流程如下:

  1. 线程 1 先获取到锁 A;
  2. 线程 1 获取到锁 B;
  3. 线程 1 开释了锁 B;
  4. 线程 1 开释了锁 A;
  5. 线程 2 获取到了锁 A;
  6. 线程 2 获取到了锁 B;
  7. 线程 2 开释了锁 B;
  8. 线程 2 开释了锁 A。

对应的实现代码如下:

import java.util.concurrent.TimeUnit;

class DeadLockTest {public static void main(String[] args) {Object lockA = new Object();
        Object lockB = new Object();
        // 创立线程 1
        Thread t1 = new Thread(() -> {
            // 1. 获取锁 A
            synchronized (lockA) {System.out.println("线程 1:取得锁 A。");
                // 休眠 1s
                try {TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                // 2. 获取锁 B
                synchronized (lockB) {System.out.println("线程 1:取得锁 B。");
                    System.out.println("线程 1:开释锁 B。");
                }
                System.out.println("线程 1:开释锁 A。");
            }
        });
        t1.start();
        // 创立线程 2
        Thread t2 = new Thread(() -> {
            try {TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            // 1. 获取锁 A
            synchronized (lockA) {System.out.println("线程 2:取得锁 A。");
                // 2. 获取锁
                synchronized (lockB) {System.out.println("线程 2:取得锁 B。");
                    System.out.println("线程 2:开释锁 B。");
                }
                System.out.println("线程 2:开释锁 A。");
            }
        });
        t2.start();}
}

以上程序的执行后果如下图所示:

总结

死锁(Dead Lock)指的是两个或两个以上的运算单元(过程、线程或协程),都在期待对方开释资源,但没有一方提前开释资源,从而造成了一种阻塞的景象就称为死锁。 产生死锁须要同时满足 4 个条件:互斥条件、申请和放弃条件、不可剥夺条件、环路期待条件,因而咱们只须要毁坏其中 1 个或多个条件就能够解决死锁的问题了

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java 面试真题解析

面试合集:https://gitee.com/mydb/interview

退出移动版