死锁的发生原因和怎么避免
“死锁(Deadlock)”是并发编程中一种严重的问题,它会导致多个线程互相等待对方释放资源,从而永久阻塞,程序不再前进。
一、什么是死锁?
死锁是指两个或多个线程在运行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们将永远等待下去。
二、死锁发生的四个必要条件(操作系统定义)
| 条件 | 含义说明 |
|---|---|
| 互斥条件 | 至少一个资源只能被一个线程占用 |
| 请求保持条件 | 一个线程持有资源的同时,再请求其他资源 |
| 不剥夺条件 | 已获取的资源不能被强制剥夺,只能线程自己释放 |
| 循环等待条件 | 若干线程之间形成了资源的循环等待链 |
只要四个条件同时满足,就可能发生死锁。
打破任何一个条件,就能避免死锁。
三、经典死锁代码示例(Java)
public class DeadlockDemo {
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("T1 持有 lockA,等待 lockB...");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockB) {
System.out.println("T1 获取 lockB");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("T2 持有 lockB,等待 lockA...");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockA) {
System.out.println("T2 获取 lockA");
}
}
});
t1.start();
t2.start();
}
}
👆 输出停在两个线程都等待对方时,不会再继续运行 —— 死锁了。
四、死锁的常见发生场景
- 多线程持有多个锁且加锁顺序不一致
- 数据库事务中,多个连接交叉锁表或记录
- 使用 synchronized 时嵌套多个锁(嵌套同步块)
- 使用
ReentrantLock.lock()不加超时处理 - 操作系统层面死锁(如文件锁、设备锁)
五、避免死锁的方法
1. 统一加锁顺序(推荐)
保证所有线程按照相同顺序获取资源。
// Always lock A before B
synchronized (lockA) {
synchronized (lockB) {
// safe code
}
}
2. 加锁时使用 tryLock + 超时机制
适用于 ReentrantLock:
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
try {
// safe code
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
👉 避免无限等待,可设置回退策略。
3. 避免嵌套锁(不必要不要获取多个锁)
- 简化代码结构
- 减少锁的持有时间
4. 使用定时锁、显式锁释放机制
- 比如使用
Lock的tryLock()而不是lock() - 或者使用
synchronized+ 尽早释放
5. 使用死锁检测工具
jstack查看线程堆栈状态
jstack <pid>
- IntelliJ IDEA、VisualVM 等支持线程图、死锁提示
6. 使用高级并发框架(如 Akka、Actor 模型)
- 避免线程直接持有共享资源
- 消息驱动的架构不会产生传统意义上的锁
六、数据库层面的死锁
在数据库中,事务间锁定同一张表的不同记录,也可能死锁。
举例:
- 事务 A:
UPDATE table WHERE id=1 - 事务 B:
UPDATE table WHERE id=2 - 然后 A 再锁 id=2,B 再锁 id=1 ⇒ 死锁!
解决方法:
- 统一更新顺序(如 always id 小的先锁)
- 控制事务范围(越小越好)
- 检查事务隔离级别(如不使用
SERIALIZABLE)
📚 七、参考资料
八、面试提问模拟总结
| 面试问题 | 答案要点 |
|---|---|
| 什么是死锁?如何发生? | 定义 + 四个必要条件 |
| 死锁常见示例? | 两线程互锁 A、B 的例子 |
| 如何避免死锁? | 加锁顺序统一、tryLock 超时、控制锁粒度等 |
| 如何排查死锁? | jstack、线程 dump 工具 |
| 数据库死锁如何避免? | 控制锁顺序、减少事务时间 |
更多详细内容请关注其他相关文章!