首页 > 编程笔记 > Java笔记 阅读:21

Java中的可重入锁(synchronized和ReentrantLock)

可重入锁是一种互斥锁,它具备“可重入”的特性。可重入的意思是在拥有锁的线程尝试再次获取该锁时,其获取锁的请求会成功,并且不会导致线程阻塞。

可重入锁的设计使得同一线程可以多次获得同一把锁,对于递归函数和对同一个资源的多层次锁定非常有用,因为它防止了线程在尝试获得它已经持有的锁时阻塞自己,这在非可重入锁中会导致死锁。

在实际的开发过程中,函数之间的调用关系可能错综复杂,一不小心就可能在多个不同的函数中反复调用 lock(),从而导致线程自身“卡死”。对于希望采用“傻瓜式”编程的我们来说,可重入锁就是用来解决这个问题的。

可重入锁使得同一个线程可以对同一把锁,在不释放的前提下反复获取,而不会导致线程卡死。因此,如果我们使用的是重入锁,那么程序就可以正常工作。唯一需要保证的是使用 unlock() 的次数和使用 lock() 的一样多。

在 Java 中,synchronized 关键字和 ReentrantLock 都是可重入锁,下面我们分别看一下两者的实现示例。

Java synchronized可重入锁

Java 的 synchronized 关键字具有可重入锁特性,具体示例代码如下:
public class ReentrantExample {
    public synchronized void outerMethod() {
        System.out.println("在外部方法中");
        innerMethod();
    }
    public synchronized void innerMethod() {
        System.out.println("在内部方法中");
        // 这里可以再次进入outerMethod(),因为这个线程已经拥有了这个对象的锁
    }
    public static void main(String[] args) {
        ReentrantExample example = new ReentrantExample();
        example.outerMethod();
    }
}
在上述代码中,当线程在 outerMethod() 中调用 synchronized 修饰的 innerMethod() 时,不会发生阻塞,因为它已经持有了锁,而且该锁具有可重入性。

Java ReentrantLock可重入锁

在 Java 中,ReentrantLock 类是 java.util.concurrent.locks 包中提供的一个可重入互斥锁,它具有与 synchronized 关键字类似的基本行为和语义,但它更灵活。

下面是一个使用 ReentrantLock 的示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    public void perform() {
        lock.lock();  // 获取锁
        try {
            System.out.println("Locked: " + Thread.currentThread().getName());
            doAdditionalWork();
        } finally {
            lock.unlock();  // 在finally块中释放锁以确保锁一定会被释放
        }
    }
    public void doAdditionalWork() {
        lock.lock();  // 再次获取锁,由于该锁是可重入的,因此这个操作是被允许的
        try {
            System.out.println("ReentrantLock: " + Thread.currentThread().getName());
        } finally {
            lock.unlock();  // 释放锁
        }
    }
    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        example.perform();
    }
}
在上述代码中,ReentrantLock 实例 lock 在 perform() 方法和 doAdditionalWork() 方法中实施锁定操作。当线程在 perform() 方法中获取锁时,它可以在 doAdditionalWork() 方法中再次获取相同的锁,而不会发生死锁,因为 ReentrantLock 允许线程进入任何已经锁定的由同一线程持有的代码块。

注意,每次调用 lock() 时都需要在 finally 块中匹配一个 unlock() 调用,以确保锁一定会被释放,防止死锁。

可重入锁的实现基于每个锁关联一个请求计数器和一个持有它的线程。当线程请求一个未被持有的锁时,JVM 会记下锁的持有者,并且将请求计数器的值设置为 1。如果同一个线程再次请求这个锁,请求计数器的值会递增。每当持有者线程退出同步块时,JVM 会将请求计数器的值递减。当请求计数器的值达到0时,锁被释放。

以下是可重入锁的几个关键功能:
Java 中的 ReentrantLock 是一个标准的可重入锁的实现,它提供了以上描述的所有功能。ReentrantLock 内部使用 AQS 作为实现同步状态管理的框架,AQS 用一个整数变量来表示锁的持有次数,并使用一个节点队列来表示线程及其等待状态。

可重入性是 Java 并发编程中一个很重要的概念,它在多线程环境下简化了锁的管理,使得编程模型更容易理解和实现,尤其是在存在复杂的锁定模式与递归锁定时。

相关文章