一、ReentrantLock 解决了什么问题
ReentrantLock 是 Java 并发包 (java.util.concurrent.locks
) 中提供的可重入互斥锁,主要解决了以下问题:
- 更灵活的同步控制:相比
synchronized
提供了更丰富的锁操作 - 公平性选择:支持公平锁和非公平锁两种策略
- 可中断的锁获取:解决了
synchronized
无法响应中断的问题 - 超时获取锁:避免线程无限期等待
- 多条件变量:单个锁可以关联多个条件队列
二、核心特性与实现原理
2.1 可重入性实现
- 内部维护一个计数器记录重入次数
- 每次 lock() 计数器+1,unlock() 计数器-1
- 计数器为0时真正释放锁
2.2 公平性实现
// 非公平锁尝试获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { // 直接尝试抢占
setExclusiveOwnerThread(current);
return true;
}
}
// ... 重入逻辑
}
// 公平锁尝试获取
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 检查是否有排队线程
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ... 重入逻辑
}
三、典型应用场景
3.1 需要可中断锁的场景
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可响应中断的获取锁
// 临界区代码
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
} finally {
if(lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
3.2 需要超时控制的场景
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区代码
} finally {
lock.unlock();
}
} else {
// 超时处理逻辑
}
3.3 复杂条件等待的场景
class BoundedBuffer {
final ReentrantLock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待非满条件
// 放入元素
notEmpty.signal(); // 唤醒非空等待
} finally {
lock.unlock();
}
}
}
四、与 synchronized 的对比
4.1 功能对比
特性 | ReentrantLock | synchronized |
---|---|---|
锁获取方式 | 显式 lock()/unlock() | 隐式获取/释放 |
公平性 | 支持公平/非公平策略 | 只有非公平 |
可中断 | lockInterruptibly() 支持 | 不支持 |
超时机制 | tryLock(timeout) 支持 | 不支持 |
条件变量 | 支持多个 Condition | 单一 wait/notify |
性能 | Java 6+ 两者接近 | Java 6+ 优化后性能相当 |
4.2 性能对比(不同场景)
-
低竞争场景:
- synchronized 有 JVM 优化优势
- ReentrantLock 稍慢(因需要方法调用)
-
高竞争场景:
- ReentrantLock 表现更稳定
- 特别是使用公平策略时
-
复杂同步场景:
- ReentrantLock 的条件变量更高效
五、优缺点分析
5.1 主要优点
-
灵活性:
- 可中断的锁获取
- 超时获取锁
- 可设置公平性策略
-
功能丰富:
- 多条件变量支持
- 提供锁状态查询方法
- 可尝试获取锁(tryLock)
-
扩展性:
- 适合实现复杂的同步控制
- 便于集成到更高级的同步工具中
5.2 主要缺点
-
使用复杂度:
- 必须显式调用 unlock()(容易遗漏导致死锁)
- 需要 try-finally 保证锁释放
-
内存开销:
- 每个 ReentrantLock 实例占用更多内存
- 相比 synchronized 有额外对象创建
-
开发成本:
- 简单场景下代码比 synchronized 冗长
- 需要更多并发编程知识才能正确使用
六、最佳实践建议
- 基本使用模板:
Lock lock = new ReentrantLock();
// ...
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
-
选择策略:
- 默认使用非公平锁(吞吐量更高)
- 只有严格需要 FIFO 顺序时才用公平锁
-
避免常见错误:
- 不要忘记在 finally 中释放锁
- 避免锁泄露(确保所有路径都释放锁)
- 不要嵌套使用不同 ReentrantLock 实例
-
监控工具:
- 使用 ThreadMXBean 检测锁竞争
- 通过 JConsole 或 VisualVM 监控锁状态
七、内部实现关键点
-
AQS 集成:
- 继承 AbstractQueuedSynchronizer
- 使用 state 字段表示锁状态
- 实现 tryAcquire/tryRelease 方法
-
非公平锁实现:
final void lock() {
if (compareAndSetState(0, 1)) // 先尝试直接获取
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入AQS队列
}
-
条件变量实现:
- 每个 ConditionObject 维护一个等待队列
- await() 将线程从同步队列移到条件队列
- signal() 将线程从条件队列移回同步队列
八 、ReentrantLock 如何通知任务队列执行
ReentrantLock 通过 Condition 机制来通知等待中的任务队列执行,这是比 synchronized
的 wait()/notify()
更灵活的通知机制。下面详细解释其工作原理和实现方式:
一、核心组件
1. Condition 对象
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // 创建条件变量
2. 两个关键队列
- 同步队列:管理所有等待锁的线程(AQS 维护的 CLH 队列)
- 条件队列:管理等待特定条件的线程(每个 Condition 对象独立维护)
二、通知机制工作流程
1. 等待流程(await)
lock.lock();
try {
while(!conditionMet) {
condition.await(); // 线程进入条件队列
}
// 条件满足后执行任务
} finally {
lock.unlock();
}
await() 内部操作:
- 创建新的 Node 节点加入条件队列
- 完全释放锁(state=0)
- 阻塞当前线程(LockSupport.park())
- 被唤醒后重新竞争锁
- 获取锁后从 await() 返回
2. 通知流程(signal/signalAll)
lock.lock();
try {
// 改变条件状态
conditionMet = true;
condition.signal(); // 唤醒一个等待线程
// 或 condition.signalAll();
} finally {
lock.unlock();
}
signal() 内部操作:
- 将条件队列中的第一个节点转移到同步队列
- 设置节点状态为 0(初始状态)
- 调用 LockSupport.unpark() 唤醒线程
三、底层实现细节
1. 条件队列结构
public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
// Node 状态
static final int CONDITION = -2;
}
2. 关键转移过程(signal)
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 执行信号传递
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 将节点转移到同步队列
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 加入同步队列
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒线程
return true;
}
四、典型应用模式
生产者-消费者模型
class TaskQueue {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Queue<Task> queue = new ArrayDeque<>();
private final int capacity;
public void put(Task task) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 等待队列非满
}
queue.add(task);
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}
public Task take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待队列非空
}
Task task = queue.remove();
notFull.signal(); // 通知生产者
return task;
} finally {
lock.unlock();
}
}
}
五、与 synchronized 的对比优势
特性 | ReentrantLock + Condition | synchronized + wait/notify |
---|---|---|
多条件支持 | 一个锁可关联多个 Condition | 每个对象只有一个等待队列 |
公平性选择 | 支持公平/非公平策略 | 只有非公平 |
精确通知 | 可指定唤醒某种条件的等待线程 | 只能随机唤醒或唤醒全部 |
超时等待 | 支持 await(timeout) | 只支持 wait(timeout) |
中断响应 | 支持可中断的等待 | 基础 wait 不可中断 |
六、注意事项
-
必须持有锁:
- await()/signal() 必须在 lock() 和 unlock() 之间调用
- 否则抛出 IllegalMonitorStateException
-
条件检查:
- 必须使用 while 循环检查条件(防止虚假唤醒)
while (!condition) { cond.await(); }
-
signal 时机:
- 在修改条件状态后再调用 signal()
- 避免先 signal 后修改条件导致通知丢失
-
资源释放:
- await() 会原子性地释放锁
- 被唤醒后会重新获取锁
ReentrantLock 通过 Condition 机制来通知等待中的任务队列执行,这是比 synchronized
的 wait()/notify()
更灵活的通知机制。下面详细解释其工作原理和实现方式:
一、核心组件
1. Condition 对象
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // 创建条件变量
2. 两个关键队列
- 同步队列:管理所有等待锁的线程(AQS 维护的 CLH 队列)
- 条件队列:管理等待特定条件的线程(每个 Condition 对象独立维护)
二、通知机制工作流程
1. 等待流程(await)
lock.lock();
try {
while(!conditionMet) {
condition.await(); // 线程进入条件队列
}
// 条件满足后执行任务
} finally {
lock.unlock();
}
await() 内部操作:
- 创建新的 Node 节点加入条件队列
- 完全释放锁(state=0)
- 阻塞当前线程(LockSupport.park())
- 被唤醒后重新竞争锁
- 获取锁后从 await() 返回
2. 通知流程(signal/signalAll)
lock.lock();
try {
// 改变条件状态
conditionMet = true;
condition.signal(); // 唤醒一个等待线程
// 或 condition.signalAll();
} finally {
lock.unlock();
}
signal() 内部操作:
- 将条件队列中的第一个节点转移到同步队列
- 设置节点状态为 0(初始状态)
- 调用 LockSupport.unpark() 唤醒线程
三、底层实现细节
1. 条件队列结构
public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
// Node 状态
static final int CONDITION = -2;
}
2. 关键转移过程(signal)
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 执行信号传递
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 将节点转移到同步队列
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 加入同步队列
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒线程
return true;
}
四、典型应用模式
生产者-消费者模型
class TaskQueue {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Queue<Task> queue = new ArrayDeque<>();
private final int capacity;
public void put(Task task) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 等待队列非满
}
queue.add(task);
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}
public Task take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待队列非空
}
Task task = queue.remove();
notFull.signal(); // 通知生产者
return task;
} finally {
lock.unlock();
}
}
}
五、与 synchronized 的对比优势
特性 | ReentrantLock + Condition | synchronized + wait/notify |
---|---|---|
多条件支持 | 一个锁可关联多个 Condition | 每个对象只有一个等待队列 |
公平性选择 | 支持公平/非公平策略 | 只有非公平 |
精确通知 | 可指定唤醒某种条件的等待线程 | 只能随机唤醒或唤醒全部 |
超时等待 | 支持 await(timeout) | 只支持 wait(timeout) |
中断响应 | 支持可中断的等待 | 基础 wait 不可中断 |
六、注意事项
-
必须持有锁:
- await()/signal() 必须在 lock() 和 unlock() 之间调用
- 否则抛出 IllegalMonitorStateException
-
条件检查:
- 必须使用 while 循环检查条件(防止虚假唤醒)
while (!condition) { cond.await(); }
-
signal 时机:
- 在修改条件状态后再调用 signal()
- 避免先 signal 后修改条件导致通知丢失
-
资源释放:
- await() 会原子性地释放锁
- 被唤醒后会重新获取锁
- ReentrantLock 的条件通知机制提供了比传统 wait/notify 更灵活、更可控的线程间协作方式,特别适合实现复杂的生产者-消费者模式或多条件等待场景。