ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中最核心的显式锁实现,基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器) 构建,支持可重入、公平 / 非公平选择、可中断、超时获取等特性,其设计核心是通过 AQS 管理锁的竞争状态和等待队列,同时通过自身逻辑实现可重入等增强功能。
要理解 ReentrantLock 的原理,需先明确其核心依赖(AQS),再拆解锁的获取、释放、可重入、公平 / 非公平等关键机制。
一、核心依赖:AQS 基础铺垫
ReentrantLock 的底层逻辑完全依赖 AQS,AQS 是 Java 并发包的 “同步骨架”,其核心作用是:
- 维护一个 volatile 修饰的状态变量
state:表示锁的占用状态(0 = 无锁,>0 = 有锁,支持可重入)。 - 维护一个 FIFO 双向等待队列:存放竞争锁失败的线程(阻塞状态)。
- 提供 CAS 操作:原子修改
state和队列节点,保证并发安全。 - 提供 模板方法:由子类(ReentrantLock 的内部同步器)实现
tryAcquire()(获取锁逻辑)、tryRelease()(释放锁逻辑)等核心方法,AQS 负责队列管理、线程阻塞 / 唤醒等通用逻辑。
ReentrantLock 内部通过两个内部类实现 AQS 的模板方法:
NonfairSync:非公平锁(默认),性能更优。FairSync:公平锁,按线程等待顺序分配锁。
两者的核心差异仅在于 锁获取时的竞争逻辑,其他机制(可重入、释放锁、队列管理)完全一致。
二、ReentrantLock 核心机制拆解
1. 锁的状态管理:state 变量的作用
AQS 的 state 是 ReentrantLock 状态的核心载体,含义如下:
state = 0:锁未被任何线程持有(无锁状态)。state > 0:锁被某线程持有,state的值等于该线程的 重入次数(支持可重入)。
例如:线程 A 第一次获取锁时,state 从 0 变为 1;若线程 A 再次重入(如递归调用加锁方法),state 变为 2;每次释放锁时,state 减 1,直到 state = 0 时,锁才真正释放。
2. 非公平锁(默认)的获取与释放流程
非公平锁的核心是:新竞争锁的线程可 “插队”,无需排队(优先尝试 CAS 获取锁,失败后再入队),这是其性能优于公平锁的关键。
(1)锁获取流程(lock() 方法)
// ReentrantLock 的 lock() 方法(默认调用 NonfairSync 的 lock())
public void lock() {
sync.lock(); // sync 是 NonfairSync 或 FairSync 实例
}
// NonfairSync 的 lock() 实现
final void lock() {
// 第一步:尝试 CAS 抢锁(插队逻辑):state 从 0 改为 1
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread()); // 标记当前线程为锁持有者
} else {
// 抢锁失败,调用 AQS 的 acquire() 模板方法(通用逻辑)
acquire(1);
}
}
// AQS 的 acquire() 模板方法(通用逻辑)
public final void acquire(int arg) {
// 第二步:调用 NonfairSync 的 tryAcquire() 再次尝试获取锁(可重入逻辑)
if (!tryAcquire(arg) &&
// 第三步:尝试失败,将当前线程封装为节点加入等待队列,然后阻塞
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt(); // 若线程在等待中被中断,标记中断状态
}
}
关键步骤拆解:
-
插队抢锁:新线程先通过
CAS(0,1)直接尝试获取锁(不排队),成功则直接持有锁。 -
tryAcquire () 逻辑(可重入 + 再次抢锁) :若插队失败(state≠0),调用
tryAcquire()处理两种情况:- 情况 1:当前线程是锁的持有者(可重入):
state += arg(arg=1),返回成功。 - 情况 2:当前线程不是持有者:返回失败,进入队列。
- 情况 1:当前线程是锁的持有者(可重入):
// NonfairSync 的 tryAcquire() 实现
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// ReentrantLock 父类的 nonfairTryAcquire()(核心逻辑)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 锁未被持有,再次尝试 CAS 抢锁(仍允许插队)
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 当前线程是持有者(可重入)
int nextc = c + acquires;
if (nextc < 0) // 防止重入次数溢出(int 最大值)
throw new Error("Maximum lock count exceeded");
setState(nextc); // 重入次数+1(无需 CAS,因为只有持有者能执行)
return true;
}
return false; // 其他线程,抢锁失败
}
- 入队阻塞:
tryAcquire()失败后,AQS 会调用addWaiter()将当前线程封装为 排他节点(Node.EXCLUSIVE)加入等待队列尾部,再通过acquireQueued()让线程阻塞(通过LockSupport.park()暂停线程),等待被唤醒。
(2)锁释放流程(unlock() 方法)
ReentrantLock 必须手动调用 unlock() 释放锁(需在 finally 中执行,避免锁泄漏),释放逻辑核心是 递减重入次数,直到 state=0 时真正释放锁(唤醒队列头节点) 。
// ReentrantLock 的 unlock() 方法
public void unlock() {
sync.release(1); // 调用 AQS 的 release() 模板方法
}
// AQS 的 release() 模板方法(通用逻辑)
public final boolean release(int arg) {
// 第一步:调用 ReentrantLock 的 tryRelease() 释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h); // 第二步:唤醒等待队列的头节点(下一个竞争锁的线程)
}
return true;
}
return false;
}
// ReentrantLock 的 tryRelease() 实现(核心释放逻辑)
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 重入次数-1
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException(); // 非持有者释放锁,抛异常
}
boolean free = false;
if (c == 0) { // 重入次数减为 0,锁真正释放
free = true;
setExclusiveOwnerThread(null); // 清除锁持有者标记
}
setState(c); // 更新 state(c≠0 时仅递减重入次数,未释放锁)
return free;
}
关键说明:
- 释放锁时,只有锁的持有者才能执行(否则抛异常)。
- 若重入次数未减到 0(
c≠0),仅更新state,锁仍被当前线程持有,不会唤醒队列。 - 只有
c=0时,才会清除持有者标记,并调用unparkSuccessor()唤醒队列头节点的下一个有效线程(被唤醒的线程会再次尝试获取锁)。
3. 公平锁的核心差异:排队抢锁,禁止插队
公平锁与非公平锁的唯一区别的是 tryAcquire() 方法:公平锁会先检查等待队列是否有线程在排队,若有则当前线程必须入队,禁止插队;若无队列才尝试 CAS 获取锁。
// FairSync 的 tryAcquire() 实现(对比非公平锁)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键差异:先调用 hasQueuedPredecessors() 检查队列是否有等待线程
if (!hasQueuedPredecessors() && // 队列无等待线程(公平性保证)
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 可重入逻辑与非公平锁一致
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// AQS 的 hasQueuedPredecessors():判断队列是否有线程在等待(公平性核心)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 队列不为空(h≠t),且队列头的下一个节点(s)不是当前线程 → 有线程排队
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁的优缺点:
- 优点:保证线程等待的公平性(先到先得),避免 “饥饿”(某线程长期抢不到锁)。
- 缺点:性能略低(需频繁检查队列,减少了 “插队” 的高效场景,增加线程切换开销)。
4. 可重入机制的底层实现
ReentrantLock 的可重入性(同一线程可多次获取同一锁),依赖两个核心设计:
- AQS 的
state变量记录重入次数:线程第一次获取锁时state=1,每次重入state+1,释放时state-1,直到state=0才释放锁。 - 锁持有者标记:AQS 维护
exclusiveOwnerThread变量(记录当前锁的持有者线程),tryAcquire()时会检查当前线程是否为持有者,若是则直接递增state,无需竞争。
核心逻辑在 nonfairTryAcquire() 和 fairTryAcquire() 中:当 state≠0 时,判断当前线程是否为 exclusiveOwnerThread,若是则执行 state += acquires,实现重入。
5. 条件变量(Condition)机制
ReentrantLock 支持通过 newCondition() 创建多个条件变量(Condition),实现更精细的线程协作(如生产者 - 消费者模型中,可区分 “空队列等待” 和 “满队列等待”),而 synchronized 仅支持一个等待队列(wait()/notify())。
Condition 的核心原理:
- 每个 Condition 对应一个 独立的等待队列(与 AQS 的主等待队列分离)。
- 调用
condition.await()时:当前线程会释放锁,从 AQS 主队列移除,加入 Condition 的等待队列,然后阻塞。 - 调用
condition.signal()时:从 Condition 等待队列头部唤醒一个线程,将其转移到 AQS 主队列,参与锁竞争。
关键流程示例:
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition(); // 条件变量:队列非空
Condition notFull = lock.newCondition(); // 条件变量:队列未满
// 消费者线程(等待队列非空)
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 释放锁,加入 notEmpty 的等待队列,阻塞
}
// 消费数据
notFull.signal(); // 唤醒 notFull 等待队列中的生产者线程
} finally {
lock.unlock();
}
// 生产者线程(等待队列未满)
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 释放锁,加入 notFull 的等待队列,阻塞
}
// 生产数据
notEmpty.signal(); // 唤醒 notEmpty 等待队列中的消费者线程
} finally {
lock.unlock();
}
底层逻辑:
- Condition 是 AQS 的内部类,其等待队列由
Node节点组成(与 AQS 主队列节点结构一致)。 await()方法会调用release()释放锁,再将线程加入 Condition 队列,最后通过LockSupport.park()阻塞。signal()方法会将 Condition 队列的头节点转移到 AQS 主队列,等待被unpark()唤醒后竞争锁。
6. 可中断与超时获取锁
ReentrantLock 支持两种增强的锁获取方式,底层依赖 AQS 的中断机制和超时逻辑:
(1)可中断获取锁(lockInterruptibly())
线程在等待锁的过程中,可被 interrupt() 中断,避免无限期等待。
- 原理:AQS 的
acquireInterruptibly()模板方法会检查线程的中断状态,若被中断则抛出InterruptedException,并退出等待。
(2)超时获取锁(tryLock(long timeout, TimeUnit unit))
线程尝试获取锁,若超过指定时间仍未获取则返回 false,避免死锁风险。
- 原理:AQS 的
tryAcquireNanos()模板方法会计算超时时间,在等待过程中定期检查是否超时,超时则返回失败,线程无需继续阻塞。
三、ReentrantLock 与 synchronized 的核心差异
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 底层实现 | 基于 AQS(Java 代码实现) | 基于 JVM 原生 monitor 锁(C++ 实现) |
| 锁获取 / 释放 | 手动调用 lock()/unlock()(需 finally) | 自动加锁 / 释放(进入 / 退出同步块) |
| 公平性 | 支持公平 / 非公平(构造函数指定) | 仅非公平锁 |
| 可中断性 | 支持(lockInterruptibly()) | 不支持(线程等待时无法中断) |
| 超时获取 | 支持(tryLock(timeout)) | 不支持 |
| 条件变量 | 支持多个 Condition(精细协作) | 仅支持一个等待队列(wait()/notify()) |
| 锁状态查询 | 支持(isLocked()、getHoldCount()) | 不支持 |
四、核心总结
ReentrantLock 的锁机制可概括为:
- 核心骨架:基于 AQS 实现,通过
state变量管理锁状态,通过 FIFO 队列管理等待线程。 - 锁竞争逻辑:非公平锁(默认)支持 “插队” 抢锁,公平锁按队列顺序抢锁,平衡性能与公平性。
- 可重入实现:通过
state记录重入次数 +exclusiveOwnerThread标记持有者,同一线程可多次获取锁。 - 增强特性:依赖 AQS 的中断、超时机制,以及 Condition 条件队列,实现可中断、超时获取、多条件协作。
- 手动管理:需手动释放锁(
finally中),避免锁泄漏,灵活性更高但需注意使用规范。
适用场景:复杂并发场景(如需要中断、超时、公平性、多条件协作),简单场景下 synchronized 更简洁(JVM 优化充分,性能接近 ReentrantLock)。