可重入锁的引入:不可重入锁中的死锁问题

重入锁(ReentrantLock),顾名思义,就是支持重复进入的锁,它表示该锁能够支持同一个线程对一个资源的重复加锁。也就是说,如果某个线程试图获得一个已经由它自己持有的可重入锁,那个这个请求就会成功。

相反,不可重入锁就是指该锁不支持同一个线程对一个资源的重复加锁,由于 Java 中并没有现成的可重入锁可供使用,下面简单实现一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NonReentrantLock {
private boolean isLocked = false;

public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
}

public synchronized void unlock() {
isLocked = false;
notify();
}
}

其中,isLocked 变量用于表示锁是否被占用的标志,lock 和 unlock 方法都是同步方法,确保在多线程环境下只有一个线程可以获得/释放锁,当锁已经被占用时,线程将通过调用在 while 循环中调用 wait 方法进行等待,直到锁被释放。

之后,我们再来看一个「不可重入锁中的死锁问题」:

下面是一个 Parent 类,它包含了一个不可重入锁 nonReentrantLock 和一个 method 方法 。在 method 方法中使用不可重入锁加锁,并且打印一条语句,之后在 finally 块中释放锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Parent {

public final NonReentrantLock nonReentrantLock = new NonReentrantLock();

public void method() throws InterruptedException {
nonReentrantLock.lock();
try {
System.out.println("Parent method");
} finally {
nonReentrantLock.unlock();
}
}
}

现在有一个 Child 类,它继承自 Parent ,并覆盖了 method 方法,并且也在其中使用了不可重入锁进行加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Child extends Parent {

@Override
public void method() throws InterruptedException {
nonReentrantLock.lock();
try {
System.out.println("Child method");
super.method();
} finally {
nonReentrantLock.unlock();
}
}
}

下面编写测试类进行测试:

1
2
3
4
5
6
7
public class Main {

public static void main(String[] args) throws InterruptedException {
Child child = new Child();
child.method();
}
}

输出:

![屏幕截图 2025-07-05 161102](..\img\屏幕截图 2025-07-05 161102.png)

可以看到,程序只显示了 Child 类的打印语句,并且一直在运行,说明发生了死锁。

分析:

由于 Child 和 Parent 类中的 method 方法执行时都需要先加锁,即每个 method 方法在执行前都会获取 Parent 中的 nonReentrantLock 锁。

当程序运行到 Child 类的 super.method() 时,因为 nonReentrantLock 锁是不可重入锁,且线程在调用 child.method() 时已经持有了这个锁,所以线程会在此时永远停顿下去,等待一个永远也无法获得的锁。进而发生死锁。

解决:

想要解决这个问题非常简单:使用可重入锁。例如使用 ReentrantLock ,或者仅使用 synchronized 关键字修饰两个类的 method 方法即可。


可重入锁的引入:不可重入锁中的死锁问题
http://example.com/2025/07/05/可重入锁的引入:不可重入锁中的死锁问题/
作者
無鎏雲
发布于
2025年7月5日
许可协议