死锁(Dead Lock)指的是两个或两个以上的运算单元(进程、线程或协程),互相持有对方所需的资源,导致它们都无法向前推进,从而导致永久阻塞的问题就是死锁。

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

死锁的代码实现如下:

public class DeadlockDemo {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1 acquired lock1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock2) {
                    System.out.println("Thread 1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2 acquired lock2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock1) {
                    System.out.println("Thread 2 acquired lock1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

在上面的示例中,我们创建了两个锁 lock1 和 lock2,并在两个线程中分别获取这两个锁,但是获取的顺序不同。当 thread1 获取 lock1 后,它会在持有锁 lock1 的情况下尝试获取 lock2,而当 thread2 获取 lock2 后,它会在持有锁 lock2 的情况下尝试获取 lock1。 如果这两个线程启动后,thread1 先获取 lock1 并且在获取 lock2 之前休眠,那么 thread2 就会获取 lock2,然后在尝试获取 lock1 时被阻塞。此时,thread1 就会在获取 lock2 时被阻塞。两个线程都在等待对方释放锁,从而形成了死锁。

死锁 4 大条件

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

  1. 互斥条件:指运算单元(进程、线程或协程)对所分配到的资源具有排它性,也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
  2. 请求和保持条件:指运算单元已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它运算单元占有,此时请求运算单元阻塞,但又对自己已获得的其它资源保持不放。
  3. 不可剥夺条件:指运算单元已获得的资源,在未使用完之前,不能被剥夺。
  4. 环路等待条件:指在发生死锁时,必然存在运算单元和资源的环形链,即运算单元正在等待另一个运算单元占用的资源,而对方又在等待自己占用的资源,从而造成环路等待的情况。

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

解决死锁

死锁的常用解决方案有以下两个:

  1. 按照顺序加锁:尝试让所有线程按照同一顺序获取锁,从而避免死锁。
  2. 设置获取锁的超时时间:尝试获取锁的线程在规定时间内没有获取到锁,就放弃获取锁,避免因为长时间等待锁而引起的死锁。

死锁排查工具

有一些工具可以帮助排查死锁问题,常见的工具有以下几个:

  1. jstack:可以查看 Java 应用程序的线程状态和调用堆栈,可用于发现死锁线程的状态。
  2. jconsole 和 JVisualVM:这些是 Java 自带的监视工具,可以用于监视线程、内存、CPU 使用率等信息,从而帮助排查死锁问题。
  3. Thread Dump Analyzer(TDA):是一个开源的线程转储分析器,可用于分析和诊断 Java 应用程序中的死锁问题。
  4. Eclipse TPTP:是一个开源的性能测试工具平台,其中包含了一个名为 Thread Profiler 的工具,可以用于跟踪线程运行时的信息,从而诊断死锁问题。

以上内容来自我的 《Java 面试突击训练营》,这门课程是 有着 14 年工作经验(前 360 开发工程师),9 年面试官经验的我,花费 4 年时间打磨完成的一门视频面试课

整个课程从 Java 基础到微服务 Spring Cloud、从实际开发问题到场景题应有尽有,如下图所示:

全程通过视频直播 + 录播的方式,把 Java 常见的面试题系统的过一遍,遇到一个问题,把这个问题相关的内容都给大家讲明白,并且视频支持永久更新和观看。

上完训练营的课程之后,基本可以应对目前市面上绝大部分公司的面试了,想要了解详情,加我微信:GG_Stone【备注:训练营】