Jakob Jenkov 2014-06-23
重入锁死(Reentrance Lockout)是一种与死锁(Deadlock)和嵌套监视器锁死(Nested Monitor Lockout)类似的情况。在关于锁(Locks)和读写锁(Read / Write Locks)的文章中,也部分涉及了重入锁死的内容。
当一个线程试图重新进入一个不可重入的 Lock、ReadWriteLock 或其他同步器时,就可能发生重入锁死。所谓“可重入”(reentrant),是指一个已经持有某个锁的线程可以再次获取该锁。Java 中的 synchronized 块是可重入的。因此,下面这段代码可以正常运行,不会出现问题:
public class Reentrant {
public synchronized void outer() {
inner();
}
public synchronized void inner() {
// 执行某些操作
}
}
注意,outer() 和 inner() 都被声明为 synchronized,这在 Java 中等价于 synchronized(this) 块。如果一个线程调用了 outer(),那么它在 outer() 内部调用 inner() 是没有问题的,因为这两个方法(或代码块)都是在同一个监视器对象(即 this)上同步的。
如果一个线程已经持有了某个监视器对象的锁,那么它就有权访问所有在该监视器对象上同步的代码块。这种特性称为重入性(reentrance)。线程可以重新进入任何它已经持有锁的代码块。
然而,下面这个 Lock 的实现不是可重入的:
public class Lock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
如果一个线程在未调用 unlock() 的情况下两次调用 lock(),那么第二次调用 lock() 将会阻塞。这时就发生了重入锁死。
如何避免重入锁死?
要避免重入锁死,你有两个选择:
- 避免编写会重复进入锁的代码
- 使用可重入锁
哪种方案更适合你的项目,取决于具体情况。通常,可重入锁的性能不如非可重入锁,并且实现起来更复杂。但在你的应用场景中,这可能并不是一个问题。是否使用支持重入的锁,需要根据具体情况进行权衡。