多线程为何会死锁
多线程出现死锁(Deadlock),本质上是因为多个线程互相持有对方需要的资源,并且都在等待对方释放,导致谁也无法继续执行。
下面从原因、条件、示例、避免方法几个方面说明。
一、死锁产生的根本原因
多个线程对多个共享资源,以不同顺序加锁,并且不释放已持有的锁。
二、死锁的四个必要条件(必须同时满足)
只要破坏其中任意一个,死锁就不会发生。
- 互斥条件
- 资源一次只能被一个线程占用
- 占有且等待
- 线程已经持有至少一个资源,又在等待其他资源
- 不可抢占
- 资源不能被强制释放,只能由持有者主动释放
- 循环等待
- 存在线程之间的资源等待环
✅ 死锁 = 这四个条件同时成立
三、经典死锁示例(Java)
Object lockA = new Object();
Object lockB = new Object();
// 线程1
new Thread(() -> {
synchronized (lockA) {
System.out.println("线程1 持有 A");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockB) {
System.out.println("线程1 获得 B");
}
}
}).start();
// 线程2
new Thread(() -> {
synchronized (lockB) {
System.out.println("线程2 持有 B");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockA) {
System.out.println("线程2 获得 A");
}
}
}).start();
执行过程
- 线程1 持有 A,等待 B
- 线程2 持有 B,等待 A
- 互相等待 → 死锁
四、常见导致死锁的场景
1️⃣ 锁顺序不一致
线程1:A → B
线程2:B → A
2️⃣ 嵌套 synchronized / 锁
- 一个方法里锁多个资源
- 调用链中层层加锁
3️⃣ 线程间相互等待
join()wait()/notify()使用不当
4️⃣ 数据库场景
- 事务 A 锁 id=1,等 id=2
- 事务 B 锁 id=2,等 id=1
五、如何避免死锁(重点)
✅ 1. 统一加锁顺序(最常用)
// 所有线程都先锁 A,再锁 B
✅ 2. 减少锁的持有时间
- 锁粒度尽量小
- 不在锁中做耗时操作
✅ 3. 使用超时机制
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// do work
} finally {
lock.unlock();
}
}
✅ 4. 避免嵌套锁
- 能用一个锁解决就不用两个
✅ 5. 使用并发工具类
ConcurrentHashMapBlockingQueueCountDownLatch
六、如何排查死锁
Java 中
jps
jstack
jstack 会显示:
Found one Java-level deadlock
七、一句话总结
多线程死锁是因为:多个线程在持有锁的同时,又去等待对方释放锁,形成了循环等待。
如果你愿意,我可以:
- 用 C++ / Python 给你写死锁示例
- 讲 数据库死锁 vs 线程死锁
- 结合 面试常问点 给你总结一版速记答案