线程死锁经常被大厂考察,下面我来详解线程死锁,以及解决方案@mikechen
线程死锁是什么
线程死锁指的是:两个或多个线程在执行过程中,因争夺资源而形成一种相互等待的局面,导致彼此都无法继续执行。

最常见的场景是多个线程按不同顺序获取多个锁。
例如:
- 线程 1 先拿到锁 A,再尝试拿锁 B;
- 线程 2 先拿到锁 B,再尝试拿锁 A;
如果线程 1 和线程 2 同时执行,就可能出现:
- 线程 1 持有 A,等待 B;
- 线程 2 持有 B,等待 A;
此时双方都在等待对方释放锁,程序进入死锁状态。
多线程死锁

1. 统一加锁顺序
这是最常用、最有效的方法。
规定所有线程必须按照相同的顺序获取锁,例如都先拿 A 再拿 B,这样就可以避免循环等待。
// 线程 1
synchronized (lockA) {
synchronized (lockB) {
// ...
}
}
// 线程 2
synchronized (lockA) {
synchronized (lockB) {
// ...
}
}
2. 减少锁的持有时间
尽量缩小同步代码块的范围,避免在持有锁时执行耗时操作,如:
- 网络请求;
- 数据库访问;
- 文件读写;
- 复杂计算;
锁持有时间越短,死锁概率越低,比如:
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public boolean execute() throws InterruptedException {
if (!lockA.tryLock(500, TimeUnit.MILLISECONDS)) {
return false;
}
try {
if (!lockB.tryLock(500, TimeUnit.MILLISECONDS)) {
return false;
}
try {
// 执行业务逻辑
return true;
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
3. 避免嵌套锁
尽量不要在一个锁内部,再申请另一个锁,比如:
synchronized (lockA) {
methodB(); // methodB 内部又获取 lockB
}
更好的方式是,尽量只持有一把锁。
不要在持锁期间调用外部方法,不要在持锁期间执行网络请求、RPC、磁盘 I/O。