不可重入锁确实可能导致死锁,特别是在同一线程尝试多次获取同一把锁时。如果锁是不可重入的,那么线程在第二次尝试获取锁时会永远阻塞,从而导致死锁。
不可重入锁与死锁的关系
不可重入锁不允许同一个线程多次获取同一把锁。在以下情况下,这种限制会导致死锁:
- 递归调用时: 如果一个方法使用了不可重入锁并递归调用自身,那么在递归调用的过程中,线程会尝试重新获取同一把锁,而由于锁是不可重入的,线程会阻塞在第二次锁请求上,最终导致死锁。
- 嵌套调用时: 如果一个方法调用了另一个也需要同一把锁的方法,同样会因为不可重入锁导致死锁。
示例:不可重入锁导致死锁
代码示例:
假设我们有一个不可重入锁:
class NonReentrantLock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait(); // 如果锁已经被占用,等待释放
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
public class DeadlockExample {
private final NonReentrantLock lock = new NonReentrantLock();
public void method1() throws InterruptedException {
lock.lock();
System.out.println("Method1: Lock acquired");
// 调用另一个需要同一把锁的方法
method2();
lock.unlock();
System.out.println("Method1: Lock released");
}
public void method2() throws InterruptedException {
lock.lock(); // 再次尝试获取锁
System.out.println("Method2: Lock acquired");
lock.unlock();
System.out.println("Method2: Lock released");
}
public static void main(String[] args) {
DeadlockExample example = new DeadlockExample();
new Thread(() -> {
try {
example.method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
运行结果:
程序会在 method2()
尝试获取锁时发生死锁,因为:
method1()
已经持有了不可重入锁。method2()
需要获取同一把锁,但由于锁是不可重入的,线程会阻塞在lock()
调用处。method1()
无法继续执行释放锁,导致死锁。
为什么可重入锁不会导致死锁?
可重入锁允许同一个线程多次获取同一把锁,而不会被阻塞。这是通过计数器机制实现的:
- 当一个线程第一次获取锁时,计数器加1。
- 如果该线程再次获取同一把锁,计数器继续加1。
- 每次释放锁时,计数器减1,只有当计数器归零时,锁才真正释放。
因此,在递归调用或嵌套调用时,线程可以多次安全地获取锁,不会导致死锁。
如何避免不可重入锁导致的死锁?
- 使用可重入锁:
- 替换不可重入锁为可重入锁(例如,Java 中的
ReentrantLock
或使用synchronized
)。
- 替换不可重入锁为可重入锁(例如,Java 中的
- 设计避免嵌套锁定:
- 如果锁是不可重入的,尽量避免在同一线程中多次尝试获取同一把锁。
- 重构代码:
- 如果可能,将涉及不可重入锁的嵌套调用重构为非嵌套调用。
总结
不可重入锁确实会导致死锁,尤其是在递归调用或嵌套调用时。如果代码中存在这种场景,建议使用可重入锁(例如 ReentrantLock
或 synchronized
)来避免死锁问题。