死锁产生的完整链条:
互斥 → 持有并等待 → 非抢占 → 循环等待 → 死锁
-
互斥:资源只能独占使用(必要条件)
-
持有并等待:线程持有资源同时请求新资源
-
非抢占:已分配资源不能被强制剥夺
-
循环等待:线程间形成资源等待环
破坏各个条件的策略对比:
| 破坏的条件 | 可行性 | Java实现方法 | 缺点 |
|---|---|---|---|
| 互斥 | 困难 | 使用无锁数据结构、原子变量 | 不适用所有场景 |
| 持有并等待 | 可行 | 原子性地获取所有资源 | 可能降低并发性 |
| 非抢占 | 可行 | 使用tryLock、超时机制 | 需要回退重试逻辑 |
| 循环等待 | 最可行 | 固定资源获取顺序 | 需要全局排序策略 |
实际工程建议
避免死锁的最佳实践:
-
尽量使用单锁:减少多锁交互的机会
-
固定锁顺序:全局约定锁的获取顺序
-
使用超时锁:用Lock接口的tryLock方法代替synchronized,这样可以设置超时时间,避免无限期等待
-
使用更高级的并发工具:如使用java.util.concurrent包中的并发类,它们设计时已经考虑了死锁问题
-
静态分析工具:使用FindBugs、SpotBugs检测潜在死锁
-
监控和检测:使用ThreadMXBean.findDeadlockedThreads()
实际使用 -- 典型的死锁示例:
如果两个线程分别同时执行method1和method2,可能会发生以下情况:
-
线程A进入method1,获得lock1。
-
线程B进入method2,获得lock2。
-
线程A在获得lock1的状态下试图获取lock2,但lock2被线程B持有,所以线程A等待。
-
线程B在获得lock2的状态下试图获取lock1,但lock1被线程A持有,所以线程B等待。
这样,两个线程互相等待对方释放锁,导致死锁。
进一步带入到死锁的产生的完整链条:
-
线程A进入method1,获得lock1(锁(这个资源)独占,达成互斥条件)。
-
线程B进入method2,获得lock2(锁(这个资源)独占,达成互斥条件)。
-
线程A在获得lock1的状态下试图获取lock2(线程持有资源同时请求新资源),但lock2被线程B持有(抢不到,非抢占),所以线程A等待(循环等待)。
-
线程B在获得lock2的状态下试图获取lock1(线程持有资源同时请求新资源),但lock1被线程A持有(抢不到,非抢占),所以线程B等待(循环等待)。
死锁达成。
单个锁也可能发生死锁问题(永久等待)
java
class Resource {
private final Object mLock = new Object();
private boolean available = false;
public void acquire() throws InterruptedException {
synchronized (mLock) {
while (!available) {
mLock.wait(); // 释放锁,等待被唤醒
}
available = false;
}
}
public void release() {
synchronized (mLock) {
available = true;
mLock.notify(); // 唤醒
}
}
}
// 问题:如果release()从未被调用,那么acquire()线程将永远等待(排除虚假唤醒和线程中断的程序逻辑外的情况)
拓展 -- 读写锁实现(简单实现):
java
import java.util.concurrent.locks.ReentrantReadWriteLock;
// 1. 读读不互斥(多个读可以同时进行)
// 2. 读写互斥(写时不能读,读时不能写)
// 3. 写写互斥(同一时间只有一个能写)
public class CorrectReadWriteLock {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private int data;
public void read() {
readLock.lock(); // 多个读线程可以同时获取
try {
// 安全地读取数据
System.out.println("Read: " + data);
} finally {
readLock.unlock();
}
}
public void write(int value) {
writeLock.lock(); // 写锁独占
try {
data = value;
System.out.println("Write: " + data);
} finally {
writeLock.unlock();
}
}
}