死锁(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 个条件:
- 互斥条件 :指运算单元(过程、线程或协程)对所调配到的资源具备排它性,也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
- 申请和放弃条件 :指运算单元曾经放弃至多一个资源,但又提出了新的资源申请,而该资源已被其它运算单元占有,此时申请运算单元阻塞,但又对本人已取得的其它资源放弃不放。
- 不可剥夺条件 :指运算单元已取得的资源,在未应用完之前,不能被剥夺。
- 环路期待条件 :指在产生死锁时,必然存在运算单元和资源的环形链,即运算单元正在期待另一个运算单元占用的资源,而对方又在期待本人占用的资源,从而造成环路期待的状况。
只有以上 4 个条件同时满足,才会造成死锁。
解决死锁
死锁的罕用解决方案有以下两个:
- 依照程序加锁:尝试让所有线程依照同一程序获取锁,从而防止死锁。
- 设置获取锁的超时工夫:尝试获取锁的线程在规定工夫内没有获取到锁,就放弃获取锁,防止因为长时间期待锁而引起的死锁。
死锁排查工具
有一些工具能够帮忙排查死锁问题,常见的工具有以下几个:
- jstack:能够查看 Java 应用程序的线程状态和调用堆栈,可用于发现死锁线程的状态。
- jconsole 和 JVisualVM:这些是 Java 自带的监督工具,能够用于监督线程、内存、CPU 使用率等信息,从而帮忙排查死锁问题。
- Thread Dump Analyzer(TDA):是一个开源的线程转储分析器,可用于剖析和诊断 Java 应用程序中的死锁问题。
- Eclipse TPTP:是一个开源的性能测试工具平台,其中蕴含了一个名为 Thread Profiler 的工具,能够用于跟踪线程运行时的信息,从而诊断死锁问题。
本文已收录至《Java 面试突击》,专一 Java 面试 100 年,查看更多:www.javacn.site