如何预防和避免线程死锁?
- 线程死锁(Deadlock):
- 是多线程编程中的一种状态,指两个或多个线程相互持有对方所需的资源,同时又等待对方释放,导致所有线程永久阻塞,无法继续执行。
核心点
- 条件:资源竞争和循环等待。
- 结果:程序卡死。
1. 死锁详解
(1) 产生条件(四大必要条件)
死锁发生需同时满足以下条件: 1. 互斥条件(Mutual Exclusion): - 资源一次只能被一个线程持有。 2. 请求与保持条件(Hold and Wait): - 线程持有至少一个资源,又请求新资源。 3. 不可抢占条件(No Preemption): - 资源只能由持有者主动释放。 4. 循环等待条件(Circular Wait): - 线程间形成资源请求的闭环。
(2) 示例
public class DeadlockDemo {
public static void main(String[] args) {
String lockA = "LockA";
String lockB = "LockB";
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("T1: Locked A");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockB) {
System.out.println("T1: Locked B");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("T2: Locked B");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockA) {
System.out.println("T2: Locked A");
}
}
});
t1.start();
t2.start();
}
}
- 过程:
- T1 持有
lockA
,请求lockB
。 - T2 持有
lockB
,请求lockA
。 - 双方互相等待,形成死锁。
(3) 输出
T1: Locked A
T2: Locked B
// 程序卡住
2. 死锁的影响
- 程序行为:
- 线程永久阻塞,任务未完成。
- 系统资源:
- 占用 CPU、内存但无进展。
- 用户体验:
- 应用无响应。
3. 检测与诊断
- 工具:
- JStack:查看线程堆栈。
- 命令:
jstack <pid>
。 - 输出:显示
deadlock
和锁信息。
- 命令:
- JVisualVM:监控线程状态。
- 特征:
- 线程状态为
BLOCKED
,互相等待锁。
示例 JStack 输出
"Thread-1" blocked on lockA owned by "Thread-0"
"Thread-0" blocked on lockB owned by "Thread-1"
Found one Java-level deadlock
4. 预防与解决
(1) 预防方法
- 统一锁顺序:破坏循环等待。
- 超时机制:避免无限等待。
- 减少锁范围:降低竞争。
- 避免嵌套锁:简化资源依赖。
- 资源分配策略:一次性请求所有资源。
- 检测与恢复:监控并中断死锁。
(2) 修改示例
Thread t1 = new Thread(() -> {
synchronized (lockA) {
synchronized (lockB) { // 顺序一致
System.out.println("T1: Locked A and B");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockA) {
synchronized (lockB) { // 顺序一致
System.out.println("T2: Locked A and B");
}
}
});
(3) 使用 Lock
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();
Thread t1 = new Thread(() -> {
if (lockA.tryLock()) {
try {
if (lockB.tryLock()) {
try {
System.out.println("T1: Locked A and B");
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
});
5. 延伸与面试角度
- 与活锁对比:
- 死锁:线程卡死。
- 活锁:线程活跃但无进展(如礼让循环)。
- 实际应用:
- 数据库:事务死锁。
- 多线程:资源竞争。
- 面试点:
- 问“定义”时,提四大条件。
- 问“解决”时,提锁顺序或
tryLock
。
总结
线程死锁是多线程因资源循环等待而阻塞的现象,需满足互斥、保持、不可抢占和循环条件。预防靠顺序或超时机制。面试时,可写死锁代码或提 JStack,展示理解深度。