面试题:Java 中 ReentrantLock 的实现原理是什么?

ReentrantLock 是 Java 并发包 java.util.concurrent.locks 中的一部分,提供了一种比同步方法和同步块更灵活的锁定机制。

它支持与隐式监视器锁(通过 synchronized 关键字实现)相同的互斥锁定语义,但具有更广泛的锁操作功能,比如尝试非阻塞地获取锁、尝试带超时的获取锁等。

实现原理

1. 基于AQS(AbstractQueuedSynchronizer)

ReentrantLock 的实现依赖于 AbstractQueuedSynchronizer(简称 AQS),这是一个提供了原子状态管理、排队机制和阻塞/唤醒机制的基础框架。

AQS 使用了一个 FIFO 队列来管理等待获取锁的线程,并且通过一个单一的int类型的变量来表示锁的状态(例如,0 表示没有线程持有锁,大于0的值表示当前锁已被持有的次数)。

  • 独占模式ReentrantLock 默认使用的是独占模式,这意味着一次只有一个线程可以持有该锁。
  • 公平性与非公平性ReentrantLock 提供了两种不同的锁实现方式——公平锁和非公平锁。公平锁会按照请求锁的顺序来分配锁,而非公平锁则允许插队,即如果在释放锁的同时有其他线程尝试获取锁,则该线程可能会优先获得锁。

2. 锁的获取与释放

  • 获取锁:当一个线程试图获取锁时,它首先检查锁是否可用(即锁的状态是否为0)。如果锁是可用的,那么这个线程就会成功获取锁,并将锁的状态加1。如果锁已经被其他线程持有,那么当前线程将会被阻塞并加入到等待队列中。
  • 释放锁:当持有锁的线程调用 unlock() 方法时,锁的状态会被减1。一旦锁的状态回到0,锁就被完全释放,此时,队列中的下一个线程有机会获取锁。

3. 重入特性

ReentrantLock 支持“可重入”,这意味着同一个线程可以多次获取同一个锁而不会导致死锁。每次调用 lock() 方法都会增加锁的状态计数器,而每次调用 unlock() 方法都会减少该计数器。只有当计数器归零时,锁才真正被释放。

4. Condition接口的支持

ReentrantLock 还提供了对条件(Condition)的支持,这使得它可以用来实现更复杂的同步逻辑,如等待/通知模式。每个 ReentrantLock 实例都可以创建多个 Condition 对象,从而允许更加细粒度的控制线程间的协作。

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 在某个线程中
lock.lock();
try {
    // 等待某个条件满足
    condition.await();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

// 在另一个线程中
lock.lock();
try {
    // 满足条件后通知等待的线程
    condition.signal();
} finally {
    lock.unlock();
}

总结

ReentrantLock 的核心在于其基于 AQS 实现的高效且灵活的锁机制,支持公平与非公平锁的选择、重入特性以及条件对象的支持,使其成为处理复杂并发场景的强大工具。

相比传统的 synchronized 同步机制,ReentrantLock 提供了更多的灵活性和控制能力,但同时也要求开发者更加谨慎地管理锁的获取和释放,以避免潜在的问题如死锁等。

THE END
喜欢就支持一下吧
点赞9 分享