概述

ReentrantLock 是 Java 并发包中基于 AQS 实现的可重入独占锁。
可以先用一句话理解:它和 synchronized 都能实现互斥,但 ReentrantLock 提供了可中断、超时、公平锁和多个条件队列等更强的控制能力。
各个获取 API 的通用语义可先参考 Java Lock;本文重点解释 ReentrantLock 自身的重入、公平策略和实现流程。
可重入机制
下面用简化伪代码表示当前 JDK 实现中的核心判断。具体私有方法名会随 JDK 版本演进,不应把某个版本的 nonfairTryAcquire() 当成稳定 API:
boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (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;
}
这让一个加锁方法调用另一个加锁方法时不会被自己卡住。
常用 API
lock()
等待直到获取锁:
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
等待过程中不能通过普通中断立即取消。
lockInterruptibly()
等待锁时允许被中断:
try {
lock.lockInterruptibly();
try {
// 临界区
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
适合可能长时间等待、需要支持任务取消的场景。
tryLock()
立即尝试获取锁,不等待:
if (lock.tryLock()) {
try {
// 获取成功
} finally {
lock.unlock();
}
} else {
// 获取失败,执行降级逻辑
}
超时获取
try {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// 临界区
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取锁超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
这是它相对于 synchronized 的重要优势:线程不必无限等待。
公平锁和非公平锁
ReentrantLock 默认是非公平锁:
new ReentrantLock(); // 非公平
new ReentrantLock(true); // 公平
非公平锁
锁释放时,新来的线程可以直接尝试抢锁,不必严格排队。
优点:
- 吞吐量通常更高
- 减少线程唤醒和上下文切换
缺点:
- 排队较久的线程可能持续抢不到锁
公平锁
在存在竞争时,公平锁倾向于把锁交给等待时间最长的线程,但它不保证线程调度本身公平,也不代表严格的业务执行顺序。
优点:
- 等待时间相对均衡
- 降低线程饥饿概率
缺点:
- 上下文切换更多
- 吞吐量通常较低
无参 tryLock() 即使面对公平锁也可以在锁可用时插队。选择公平模式前应先确认业务确实需要接近先到先得,并接受吞吐量下降;不能仅凭“公平听起来更正确”来选择。
Condition 条件队列
Condition 可以理解成比 Object.wait()、notify() 更灵活的线程等待机制。一把 ReentrantLock 可以关联多个条件队列,完整流程见 Java Condition。
Condition condition = lock.newCondition();
等待:
lock.lock();
try {
while (!conditionSatisfied()) {
condition.await();
}
// 条件满足,继续执行
} finally {
lock.unlock();
}
唤醒:
lock.lock();
try {
updateState();
condition.signal();
} finally {
lock.unlock();
}
必须先持有对应的锁,才能调用:
await()signal()signalAll()
await()
线程执行 await() 时:
- 加入 Condition 等待队列
- 完全释放当前持有的锁
- 进入等待状态
- 被
signal()唤醒后,转移到锁的同步队列 - 重新竞争锁
- 获取锁后,从
await()返回
注意:signal() 并不代表被唤醒线程会立即执行。它必须等当前线程释放锁后,再重新竞争锁。
必须使用 while
错误写法:
if (queue.isEmpty()) {
notEmpty.await();
}
正确写法:
while (queue.isEmpty()) {
notEmpty.await();
}
因为线程被唤醒后:
- 条件可能已被其他线程改变;
- 可能出现虚假唤醒;
- 唤醒不等于立即获得执行权。
所以每次恢复执行后都要重新检查条件。
非公平锁获取
下面的流程图描述默认非公平模式的主要路径;它省略了取消、超时、中断和队列清理等实现细节。
flowchart TD
A["调用 lock()"] --> B{"state == 0?"}
B -->|"是"| C["CAS:state 从 0 改为 1"]
C --> D{"CAS 成功?"}
D -->|"成功"| E["记录当前线程为 Owner"]
D -->|"失败"| F["进入 AQS acquire 流程"]
B -->|"否"| G{"Owner 是当前线程?"}
G -->|"是"| H["state = state + 1<br/>完成重入"]
G -->|"否"| F
F --> I["加入 AQS Sync Queue 尾部"]
I --> J["检查前驱节点和获取资格"]
J --> K{"获取锁成功?"}
K -->|"成功"| E
K -->|"失败"| L["LockSupport.park() 挂起"]
L -->|"被唤醒"| J