如何解决死锁问题?
死锁(Deadlock) 是并发编程中常见的问题之一,发生在多个线程彼此持有对方需要的资源,且都在等待对方释放资源时,导致系统永久阻塞。
一、死锁的四个必要条件
根据“Coffman 条件”,死锁的发生必须同时满足以下四个条件:
- 互斥条件:资源一次只能被一个线程占用。
- 占有且等待:线程持有部分资源,同时等待其它资源。
- 不可抢占:线程已获得资源不能被强制释放。
- 循环等待:多个线程之间形成一种“环形”资源等待关系。
打破任一条件,都可以避免死锁。
二、死锁的经典示例(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 locked lock1");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lock2) {
System.out.println("Thread-1 locked lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread-2 locked lock2");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lock1) {
System.out.println("Thread-2 locked lock1");
}
}
});
t1.start();
t2.start();
}
}
结果:两个线程都在等待对方释放资源,产生死锁。
三、解决死锁的方法
方法 1:破坏循环等待 —— 固定锁顺序
始终以固定顺序获取锁,防止互相等待。
// 使用 hashCode 比较决定加锁顺序
Object lockA = new Object();
Object lockB = new Object();
void doSafeLocking(Object o1, Object o2) {
Object first = o1.hashCode() < o2.hashCode() ? o1 : o2;
Object second = o1.hashCode() < o2.hashCode() ? o2 : o1;
synchronized (first) {
synchronized (second) {
// 业务逻辑
}
}
}
方法 2:尝试锁(tryLock) + 超时 + 回退机制
使用 ReentrantLock.tryLock() 避免永久等待。
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
boolean acquired = false;
try {
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
acquired = true;
// 执行业务
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (acquired) {
lock2.unlock();
lock1.unlock();
}
}
防止永久阻塞,适合高并发系统。
方法 3:避免资源占用并等待
将所有需要的资源一次性获取(加锁),避免先占一部分再等另一部分。
synchronized (lock1) {
synchronized (lock2) {
// 获取全部资源后再执行
}
}
虽然代码简单,但需要任务可一次拿到所有资源才适用。
方法 4:使用线程调度或锁管理框架(高级解决方案)
- 使用如 Akka、Disruptor、Actor 模型等避免共享资源的框架。
- 数据库中使用 悲观锁 + 乐观锁 + 行级锁 + 事务超时 控制资源竞争。
- 使用工具如 Java Flight Recorder、JConsole、Arthas 来检测和诊断死锁。
四、如何检测死锁?
Java 自带命令检测(JDK 工具)
jps # 查看 Java 进程 ID
jstack <pid> # 查看线程堆栈
在输出中若发现:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001c0d0e78 (object lock2)
...
"Thread-2":
waiting to lock monitor 0x000000001c0d0e78 (object lock1)
...
即为死锁。
推荐资料
- 《Java 并发编程实战》第 10 章:死锁与活锁
- 官方文档:ReentrantLock
- 工具推荐:
jstack:线程栈分析Arthas:运行时排查工具VisualVM:GUI 可视化工具Deadlock Detector:IDE 插件
总结
| 死锁解决思路 | 说明 |
|---|---|
| 固定加锁顺序 | 打破“循环等待”条件 |
| tryLock + 超时机制 | 打破“不可抢占”条件 |
| 一次性获取所有资源 | 打破“占有且等待”条件 |
| 减少锁粒度,使用无锁结构 | 彻底规避共享资源竞争 |
更多详细内容请关注其他相关文章!