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
提供了更多的灵活性和控制能力,但同时也要求开发者更加谨慎地管理锁的获取和释放,以避免潜在的问题如死锁等。