什么是死锁?
死锁是指在多线程环境中,由于资源分配不当或进程运行顺序不合理,导致涉及的线程都处于等待状态,无法继续执行的一种状况。在Java中,死锁通常发生在多个线程互相等待对方持有的锁,但没有任何一个线程能够继续向前推进。
死锁产生的原因
- 互斥条件:每个线程需要独占资源。
- 占有和等待:线程至少持有一个资源,并且等待获取其他线程持有的资源。
- 不可抢占:资源只能由占有它的线程自愿释放。
- 循环等待:存在一组线程,每个线程都在等待下一个线程所持有的资源。
如何避免死锁
- 加锁顺序:所有线程都按照相同的顺序加锁。这样可以避免循环等待的条件。
- 加锁时限:尝试获取锁时加上超时时间,如果超时则释放所有已持有的锁并返回失败,避免无限期等待。
- 一次性申请所有资源:如果可能,设计线程一次性申请所有需要的资源,而不是分步申请。
- 检测死锁:在系统中添加检测机制,当检测到死锁时,适当地中断一些线程。
- 资源有序分配:为系统中的资源分配一个唯一的顺序,并要求每个线程按照这个顺序来请求资源。
- 使用锁的层级结构:定义锁的层级关系,线程在申请高级别锁之前必须释放低级别的锁。
- 使用
java.util.concurrent
包 :使用ReentrantLock
代替synchronized
,因为ReentrantLock
提供了尝试非阻塞地获取锁的能力,并且可以设置超时。 - 避免死循环:避免线程在持有一个锁的同时,又尝试获取另一个线程持有的锁,这可以防止循环等待的发生。
示例:避免死锁的代码实践
java
// 假设有两个资源 lock1 和 lock2
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
void withoutDeadlock() {
try {
// 尝试获取 lock1,如果获取失败则等待最多1秒
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
// 尝试获取 lock2
lock2.lock();
// 临界区代码
} finally {
lock2.unlock(); // 解锁 lock2
}
} else {
// 无法获取 lock1,在超时后的处理逻辑
}
} finally {
lock1.unlock(); // 解锁 lock1
}
}
在实际应用中,避免死锁需要对代码逻辑有深入的理解,并且可能需要对资源管理和线程调度策略进行精心设计。