死锁原理与预防
外观
死锁原理与预防[编辑 | 编辑源代码]
定义与基本条件[编辑 | 编辑源代码]
死锁(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 threadA = new Thread(() -> {
synchronized (lock1) {
System.out.println("ThreadA持有lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("ThreadA获取lock2");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lock2) {
System.out.println("ThreadB持有lock2");
synchronized (lock1) {
System.out.println("ThreadB获取lock1");
}
}
});
threadA.start();
threadB.start();
}
}
输出结果可能为:
ThreadA持有lock1 ThreadB持有lock2 (程序无限挂起)
关键点说明:
- 线程A持有`lock1`后尝试获取`lock2`
- 线程B持有`lock2`后尝试获取`lock1`
- 双方互相阻塞,形成循环等待
死锁检测与诊断[编辑 | 编辑源代码]
Java工具链[编辑 | 编辑源代码]
1. 使用`jstack`命令生成线程转储:
jstack <pid> > thread_dump.txt
2. 查找输出中的`deadlock`关键词,典型报告如下:
Found one Java-level deadlock: "ThreadB": waiting to lock monitor 0x00007f88a8003e58 (object 0x000000076ab270c8) which is held by "ThreadA" "ThreadA": waiting to lock monitor 0x00007f88a8003f98 (object 0x000000076ab270d8) which is held by "ThreadB"
可视化分析[编辑 | 编辑源代码]
预防策略[编辑 | 编辑源代码]
策略 | 实现方法 | 示例 |
---|---|---|
破坏互斥条件 | 使用共享资源(如读写锁) | `ReentrantReadWriteLock` |
破坏占有并等待 | 一次性申请所有资源 |
// 使用原子操作获取所有锁
while(!tryLockAll(lock1, lock2)) {
releaseLocks();
backoff();
}
|
破坏非抢占条件 | 设置超时机制 |
lock.tryLock(500, TimeUnit.MILLISECONDS)
|
破坏循环等待 | 规定资源请求顺序 |
// 总是先申请lock1再申请lock2
|
实际案例:数据库事务死锁[编辑 | 编辑源代码]
场景描述:
- 事务T1先更新账户A再更新账户B
- 事务T2先更新账户B再更新账户A
解决方案:
-- 规定所有事务必须按字母顺序操作账户
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;
高级话题:哲学家就餐问题[编辑 | 编辑源代码]
经典同步问题演示死锁:
解决方案:
- 限制最多4位哲学家同时就餐
- 使用资源分级(筷子编号,总是先拿小号筷子)
页面模块:Message box/ambox.css没有内容。
死锁预防会增加系统复杂度,需权衡安全性与性能 |
总结[编辑 | 编辑源代码]
- 死锁的四个必要条件缺一不可
- 预防策略围绕破坏必要条件展开
- 实际开发中应优先使用`java.util.concurrent`工具包
- 复杂系统建议结合静态分析和运行时检测