死锁发生的必要条件是互斥、占有并等待、不可剥夺、循环等待四个条件同时满足;其中循环等待最易触发,如线程a持lock1等lock2、线程b持lock2等lock1。

死锁发生的必要条件是什么
Java 中的死锁不是某个 API 或语法错误导致的,而是多个线程在争夺资源时进入一种互相等待、谁也无法继续执行的状态。它必须同时满足四个条件(Coffman 条件):互斥、占有并等待、不可剥夺、循环等待。只要其中一个不成立,死锁就不会发生。
实际编码中最容易触发的是「循环等待」:比如线程 A 持有 lock1 并试图获取 lock2,而线程 B 持有 lock2 并试图获取 lock1。此时 JVM 无法自动打破僵局,两个线程会永远阻塞在 wait() 或 synchronized 入口处。
如何复现一个典型的 Java 死锁
下面这段代码能稳定复现死锁,关键在于两个线程以相反顺序获取同一组锁:
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread-1: acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread-1: acquired lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread-2: acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread-2: acquired lock1");
}
}
});
t1.start(); t2.start();
}
}
运行后程序无输出或卡住,用 jstack <pid></pid> 查看线程栈,会看到类似这样的提示:
立即学习“Java免费学习笔记(深入)”;
Found one Java-level deadlock:============================="Thread-1": waiting to lock monitor 0x00007f8b4c00a9e8 (object 0x0000000715a0a0a0, a java.lang.Object), which is held by "Thread-2"
避免死锁的几种有效手段
没有银弹,但以下做法能显著降低风险:
- 统一锁获取顺序:所有线程按相同顺序请求锁,比如始终先
synchronized(lockA)再synchronized(lockB) - 使用带超时的锁操作:改用
ReentrantLock.tryLock(long, TimeUnit),失败就释放已持锁并重试或放弃 - 减少锁粒度和作用域:避免在
synchronized块中做耗时操作(如 I/O、远程调用) - 慎用嵌套锁:尤其不要在持有锁期间调用外部方法(对方可能也持锁),除非你完全掌控其行为
注意:synchronized 本身不支持超时或中断,这是 ReentrantLock 的核心优势之一。
如何检测和诊断运行中的死锁
JDK 自带工具足够日常排查:
-
jstack -l <pid></pid>:显示锁信息和是否处于死锁状态(加-l才能看到java.util.concurrent类锁) - JConsole → “Threads” 标签页 → 点击 “Detect Deadlock” 按钮,会高亮涉及线程及锁对象
- 代码中可定期调用
ThreadMXBean.findDeadlockedThreads()实现自动告警
生产环境建议开启 JVM 参数 -XX:+PrintConcurrentLocks(配合 -XX:+UnlockDiagnosticVMOptions),在 Full GC 日志中打印锁持有情况——但这会带来性能开销,仅限问题定位阶段启用。
真正难缠的不是明显循环等待,而是跨多层调用、混合使用 synchronized 和 ReentrantLock、或锁对象动态生成(比如用 new Object() 作为锁)的情况。这些场景下,静态分析几乎失效,必须依赖运行时观测。










