在Java多线程编程中,死锁是指两个或多个线程互相持有对方所需资源且不释放,导致所有相关线程永久阻塞。要避免死锁,需要先理解其成因并采用合理的预防策略。

死锁产生的四个必要条件
根据理论体系,死锁发生必须同时满足以下四点:
- 互斥条件:资源只能被一个线程占用
- 占有且等待:线程持有资源并等待其他资源
- 不可抢占:线程已获资源不能被强制剥夺
- 循环等待:存在线程资源的环形等待链
只要破坏其中任意一个条件,就可以避免死锁。
方法一:固定加锁顺序
循环等待常因加锁顺序不一致引起。统一所有线程获取锁的顺序可有效预防。
public class Account {
private final int id;
private int balance;
public Account(int id, int balance) {
this.id = id;
this.balance = balance;
}
// 按id大小固定顺序加锁,避免循环等待
public void transfer(Account target, int amount) {
Account first = this.id < target.id ? this : target;
Account second = this.id < target.id ? target : this;
synchronized (first) {
synchronized (second) {
this.balance -= amount;
target.balance += amount;
}
}
}
}
方法二:使用尝试锁与超时
通过ReentrantLock的tryLock设置超时,线程拿不到锁就放弃或回退,打破占有且等待。
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class TimedLock {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public boolean safeAction() throws InterruptedException {
// 尝试在1秒内获取两把锁
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
try {
// 执行操作
return true;
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
return false;
}
}
方法三:减小锁粒度与使用并发容器
尽量缩小同步范围,或用ConcurrentHashMap等并发工具替代手动加锁,降低冲突概率。
| 方式 | 说明 |
|---|---|
| 细粒度锁 | 只对必要代码块加锁 |
| 并发集合 | 利用Java并发包内置安全结构 |
| 无锁算法 | 使用原子变量如AtomicInteger |
方法四:避免长任务中持锁
占有锁期间不要调用外部慢服务或做IO,防止不可抢占条件被放大。
设计原则:锁内只做最短、最确定的操作。
小结
在Java里避免死锁的核心是预防而非事后处理。结合固定顺序、超时锁、合理并发工具与良好编码规范,可显著降低风险。