死锁
一、是什么?
死锁是多线程编程中的一种状态,当两个或多个线程永久阻塞,互相等待对方释放资源时发生。此时程序无法继续执行,需要外部干预才能恢复。
二、产生死锁的条件(什么情况下会产生?)
死锁需同时满足以下四个必要条件:
- 互斥:资源一次只能被一个线程占用(如
synchronized
锁)。 - 占有并等待:线程持有资源的同时,请求其他线程占有的资源。
- 不可剥夺:资源只能由持有者主动释放,不能被强制抢占。
- 循环等待:线程间形成环形等待链(如线程A等B的资源,线程B等A的资源)。
三、如何解决死锁
预防策略(破坏必要条件):
- 破坏互斥:使用无锁数据结构(如
ConcurrentHashMap
)。 - 破坏占有并等待:一次性申请所有所需资源(通过统一加锁)。
- 破坏不可剥夺:设定超时机制(如
Lock.tryLock(timeout)
)。 - 破坏循环等待:按固定顺序获取资源(如统一按资源ID从小到大加锁)。
- 破坏互斥:使用无锁数据结构(如
检测与恢复:
- 死锁检测:通过监控线程状态和资源依赖图识别死锁(JDK工具:
jstack
或ThreadMXBean.findDeadlockedThreads()
)。 - 强制恢复:终止部分线程或回滚操作(需设计事务机制)。
- 死锁检测:通过监控线程状态和资源依赖图识别死锁(JDK工具:
避免策略:
- 银行家算法:分配资源前预判安全性(实际开发较少用)。
- 使用高层工具:
- JDK 5+ 的
java.util.concurrent
包(如Semaphore
,CountDownLatch
)。 - JDK 8+ 的
CompletableFuture
异步编程(减少显式锁竞争)。
- JDK 5+ 的
四、应用场景
- 数据库事务中的多表更新。
- 多线程资源竞争(如转账:线程A锁账户1等账户2,线程B锁账户2等账户1)。
- 分布式系统中的跨服务资源协调。
五、Java示例
java
// 死锁示例
public class DeadlockDemo {
static final Object lock1 = new Object();
static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); }
catch (InterruptedException e) {}
synchronized (lock2) {} // 等待lock2
}
}).start();
new Thread(() -> {
synchronized (lock2) {
synchronized (lock1) {} // 等待lock1
}
}).start();
}
}
java
// 解决方案:按固定顺序加锁
public class FixedOrderLock {
static final Object lock1 = new Object();
static final Object lock2 = new Object();
public static void main(String[] args) {
// 所有线程统一先锁lock1,再锁lock2
new Thread(() -> {
synchronized (lock1) {
synchronized (lock2) {} // 安全
}
}).start();
new Thread(() -> {
synchronized (lock1) {
synchronized (lock2) {} // 安全
}
}).start();
}
}
六、重要注意事项
- 避免嵌套锁:尽量减少锁的嵌套层级。
- 锁超时机制:使用
ReentrantLock.tryLock(5, TimeUnit.SECONDS)
替代synchronized
。 - 代码审查:定期用
jstack
或可视化工具(如JConsole)检测死锁。 - 线程池管理:合理设置线程数量,避免资源过度竞争。
七、总结
死锁由互斥、占有等待、不可剥夺和循环等待四个条件共同触发。解决方案包括破坏必要条件、使用超时锁、按顺序加锁、以及采用JDK5+的高并发工具。关键是通过设计规避资源竞争环路,并利用现代并发API降低风险。