ReentrantLock的newCondition()支持一个锁创建多个独立条件变量,实现精准唤醒;如阻塞队列中notFull和notEmpty分别控制生产者与消费者,避免notifyAll的惊群效应。

在 Java 中,ReentrantLock 配合 newCondition() 可以替代 synchronized + wait/notify,实现更灵活、更精准的线程等待与唤醒控制——关键在于“一个锁可创建多个条件变量”,不同业务逻辑可以绑定各自独立的 Condition,避免无差别唤醒(如 notifyAll())带来的性能浪费和逻辑干扰。
一、为什么需要多个 Condition?
默认的 synchronized 块只有一个隐式等待队列,所有 wait() 线程挤在一起;一旦调用 notifyAll(),不管是否符合条件,全部被唤醒再竞争判断,造成“惊群效应”。而 ReentrantLock.newCondition() 允许你按语义划分等待组:
- 生产者只在“缓冲区未满”条件下等待,唤醒时只通知消费者
- 消费者只在“缓冲区非空”条件下等待,唤醒时只通知生产者
- 还可以扩展:高优先级任务等待
highPriorityCond,低优先级走lowPriorityCond
二、典型应用:阻塞队列的双条件实现
以下是一个简化但完整的双条件阻塞队列片段,体现“挂起与唤醒的精确性”:
public class BoundedQueue<t> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 生产者等待
private final Condition notEmpty = lock.newCondition(); // 消费者等待
private final Object[] items;
private int putIndex, takeIndex, count;
public BoundedQueue(int capacity) {
this.items = new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 满了就等 notFull 条件
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
++count;
notEmpty.signal(); // 只唤醒一个消费者(有数据可取)
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 空了就等 notEmpty 条件
}
Object item = items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
--count;
notFull.signal(); // 只唤醒一个生产者(有空间可放)
return (T) item;
} finally {
lock.unlock();
}
}
}</t>
注意:signal() 不会唤醒所有等待者,只唤醒一个匹配该 Condition 的线程;若需广播,才用 signalAll()。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
三、进阶技巧:避免虚假唤醒 & 条件重入
await() 可能被系统中断或虚假唤醒(spurious wakeup),因此必须配合 while 循环检查条件,不能用 if。同时,每个 Condition 是独立的等待队列,互不干扰:
- 线程 A 调用
cond1.await()→ 进入 cond1 队列,释放锁 - 线程 B 调用
cond2.signal()→ 只唤醒 cond2 队列中的线程,对 cond1 无影响 - 线程 C 在
cond1.await()返回后,仍需重新获取锁并再次验证业务条件是否真正满足
四、常见陷阱提醒
使用 newCondition() 时容易忽略的关键点:
-
Condition 必须由同一把锁创建:不同
ReentrantLock实例创建的Condition无法互通,也不能混用 -
await() 前必须已持有锁:否则抛
IllegalMonitorStateException,且await()内部会自动释放锁,唤醒后自动重新获取 - signal() 不保证立即执行:它只是将线程从等待队列移到同步队列,实际调度仍由 JVM 和 OS 决定
-
避免长时间持有锁期间调用 await():应尽快完成状态更新再
await(),防止阻塞其他线程操作共享资源
本质上,newCondition() 的价值不是“更强大”,而是“更清晰”——把不同等待原因显式建模为不同条件对象,让唤醒逻辑与业务语义对齐,减少竞争、提升可读性与可维护性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










