在Java中,死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而陷入互相等待的僵局。当以下四个必要条件同时满足时,就会发生死锁:


死锁产生的必要条件

  1. 互斥条件(Mutual Exclusion)
    资源一次只能被一个线程占用(例如:synchronized锁、数据库连接等独占资源)。

  2. 请求与保持(Hold and Wait)
    线程持有至少一个资源,同时请求其他线程占有的资源(不释放已持有资源)。

  3. 不可剥夺(No Preemption)
    线程已获得的资源在未使用完前,不能被强行剥夺(只能主动释放)。

  4. 循环等待(Circular Wait)
    存在线程资源的环形等待链:
    线程A → 等待 线程B 的资源 → 线程B → 等待 线程A 的资源。


死锁示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lock1) { // 1. 线程A获取lock1
System.out.println("ThreadA: Holding lock1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
synchronized (lock2) { // 3. 线程A尝试获取lock2(但被线程B持有)
System.out.println("ThreadA: Acquired lock2!");
}
}
});

Thread threadB = new Thread(() -> {
synchronized (lock2) { // 2. 线程B获取lock2
System.out.println("ThreadB: Holding lock2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
synchronized (lock1) { // 4. 线程B尝试获取lock1(但被线程A持有)
System.out.println("ThreadB: Acquired lock1!");
}
}
});

threadA.start();
threadB.start(); // 最终两线程互相等待,形成死锁
}
}

执行结果:

1
2
3
ThreadA: Holding lock1...
ThreadB: Holding lock2...
(程序永远卡住,无后续输出)

死锁原因分析

  1. 互斥lock1lock2 一次只能被一个线程占用。
  2. 请求与保持
    • 线程A持有 lock1 并请求 lock2
    • 线程B持有 lock2 并请求 lock1
  3. 不可剥夺:JVM 不会强制回收线程已持有的锁。
  4. 循环等待
    线程A → 等待 lock2(被线程B持有)→ 线程B → 等待 lock1(被线程A持有)。

如何避免死锁?

打破任意一个必要条件即可预防死锁:

  1. 避免嵌套锁:尽量只获取一个锁。
  2. 固定锁的顺序:所有线程按相同顺序获取锁(例如先 lock1lock2)。
  3. 使用超时机制:通过 Lock.tryLock(long timeout, TimeUnit unit) 尝试获取锁,超时则回退。
  4. 死锁检测:利用JDK工具(如jstackJConsole)监控线程状态。

修复后的代码(固定锁顺序):

1
2
3
4
5
6
7
8
9
// 所有线程必须先获取lock1,再获取lock2
Thread threadB = new Thread(() -> {
synchronized (lock1) { // 改为先获取lock1
System.out.println("ThreadB: Holding lock1...");
synchronized (lock2) { // 再获取lock2
System.out.println("ThreadB: Acquired lock2!");
}
}
});

诊断死锁工具

  1. 命令行
    1
    jstack <PID>          # 打印线程栈,会标记"Found one Java-level deadlock"
  2. JConsole/VisualVM:图形化查看线程阻塞状态,检测死锁。

总结

必要条件 解决策略
互斥 使用非阻塞数据结构(如ConcurrentHashMap
请求与保持 一次性申请所有资源(原子操作)
不可剥夺 使用可中断锁(Lock.lockInterruptibly()
循环等待 固定资源申请顺序(最常用方案)

通过合理设计锁的获取顺序和超时机制,可有效避免死锁问题。