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

本文尝试给出《Java 并发编程实战》2.3.2 节中提到的 “如果内置锁不是可重入的,那么示例代码将会发生死锁” 的一种具体实现。

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

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

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 块中释放锁。

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 方法,并且也在其中使用了不可重入锁进行加锁。

public class Child extends Parent {

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

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

public class Main {

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

输出:

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

分析:

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

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

解决:

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

参考资料:

  • [1] 《Java 并发编程实战》

  • [2] 《Java 并发编程的艺术》

posted @ 2023-04-16 18:49    阅读(258)  评论(0)    收藏  举报