Java 死锁
Java 死锁
死锁概述
死锁是多线程并发中因资源争夺导致的阻塞现象。当两个或多个线程各自持有部分资源,同时等待对方释放所需资源时,会形成相互等待的循环,所有线程均无法继续执行,程序陷入停滞。
死锁核心场景图示
考虑有两个线程和两个对象的情形,如下图所示。

线程1已获取object1锁,等待object2锁;线程2已获取object2锁,等待object1锁。两者相互等待对方释放资源,导致死锁。
代码示例
package com.deadlock;
/**
* @author Jing61
*/
public class DeadLockDemo {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " get resource1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " wait resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " get resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource1");
}, "thread-1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " get resource2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " wait resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " get resource1");
}
System.out.println(Thread.currentThread().getName() + " release resource1");
}
System.out.println(Thread.currentThread().getName() + " release resource2");
}, "thread-2").start();
}
}
运行结果:程序永远无法结束,thread-1和thread-2相互等待对方释放资源,陷入死锁。
死锁产生的四个必要条件
死锁的发生必须同时满足以下四个条件,缺一不可:
- 互斥条件:资源同一时间只能被一个线程占用,无法被多个线程共享。
- 请求与保持条件:线程已持有至少一个资源,同时又在等待获取其他线程持有的资源。
- 不剥夺条件:线程已获得的资源,在未主动释放前,不能被其他线程强行剥夺。
- 循环等待条件:多个线程形成首尾相接的循环等待链,每个线程都在等待下一个线程持有的资源。
死锁的避免方案
避免死锁的核心思路是破坏死锁的必要条件,其中最常用且简单有效的是「资源排序技术」。
资源排序技术
给所有需要争夺的锁对象指定统一的获取顺序,确保所有线程都按照该顺序获取锁。该技术是给每一个需要锁的对象指定一个顺序,确保每个线程都按照这个顺序来获取锁。例如,在上图中,假设按照 object1,object2 的顺序对两个对象进行排序。采用资源排序技术,线程 2 必须先获取 object1 上的锁,然后才能获取 object2 上的锁。一旦线程 1 获取了 object1 上的锁,线程 2 必须等待 object1 上的锁。所以,线程 1 就能获取 object2 上的锁,不会再发生死锁的现象。
优化后的代码(避免死锁)
package com.deadlock;
/**
* @author Jing61
*/
public class DeadLockDemo {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " get resource1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " wait resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " get resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource1");
}, "thread-1").start();
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " get resource1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " wait resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " get resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource2");
}
System.out.println(Thread.currentThread().getName() + " release resource1");
}, "thread-2").start();
}
}
优化逻辑:线程2不再先获取resource2,而是与线程1保持一致的锁顺序(先resource1后resource2)。即使线程1先持有resource1,线程2也会等待resource1释放,不会形成循环等待,从而避免死锁。
其他避免死锁的思路
- 限时获取锁:使用
ReentrantLock.tryLock(long timeout, TimeUnit unit),超时未获取锁则放弃,破坏「请求与保持条件」。 - 减少锁的持有时间:仅在必要的代码段加锁,执行完临界区后立即释放锁,降低锁冲突概率。
- 避免一次性获取多个资源:将多个资源的获取拆分为单次获取,减少「请求与保持」的发生概率。
死锁的检测方法
当程序疑似死锁时,可通过 JDK 自带工具检测死锁,步骤如下(运行死锁代码):
查找 Java 进程 PID
打开命令行终端,执行 jps 命令(JDK 自带,需配置环境变量),找到目标程序的进程 ID(PID)。

查看线程堆栈信息
执行 jstack <PID> 命令,分析线程状态,工具会直接标注死锁信息。


浙公网安备 33010602011771号