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();
}
}
}
注意:signal() 不会唤醒所有等待者,只唤醒一个匹配该 Condition 的线程;若需广播,才用 signalAll()。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“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() 的价值不是“更强大”,而是“更清晰”——把不同等待原因显式建模为不同条件对象,让唤醒逻辑与业务语义对齐,减少竞争、提升可读性与可维护性。

















