Java阻塞队列通过ReentrantLock配两个Condition实现精准唤醒:notEmpty供消费者等待非空,notFull供生产者等待非满,避免synchronized单等待队列的混淆与无效唤醒。

Java 阻塞队列(如 ArrayBlockingQueue、LinkedBlockingQueue)底层并不直接暴露 Condition 给用户,但其内部正是通过 ReentrantLock + Condition 实现“精准唤醒”的——即:生产者只唤醒等待消费的线程,消费者只唤醒等待生产的线程,避免无差别 notifyAll() 带来的无效调度。
为什么需要 Condition 而不是 synchronized?
synchronized 只有一个隐式等待队列和一个 wait()/notify() 机制,无法区分“等数据”和“等空间”两类等待者。而阻塞队列需同时支持:
- 消费者在空时阻塞 → 等待“非空”条件
- 生产者在满时阻塞 → 等待“非满”条件
用一个锁配两个独立 Condition,就能让这两类线程分别挂起、分别唤醒,互不干扰。
ArrayBlockingQueue 中的两个 Condition 实例
以 ArrayBlockingQueue 为例,它在构造时创建:
立即学习“Java免费学习笔记(深入)”;
final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); // 消费者等待这个 private final Condition notFull = lock.newCondition(); // 生产者等待这个
关键逻辑节选:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
put(E e):加锁后,若队列满,调用
notFull.await();插入成功后,调用notEmpty.signal()唤醒一个等待消费的线程 -
take():加锁后,若队列空,调用
notEmpty.await();取走元素后,调用notFull.signal()唤醒一个等待生产的线程
注意:用的是 signal() 而非 signalAll(),因为只需唤醒一个对应角色的线程,减少上下文切换开销。
自己手写精准唤醒队列的关键点
若想基于 ReentrantLock 和 Condition 实现简易阻塞队列,需注意:
- 所有共享状态访问(如
count、items[])必须在锁保护下进行 - 判断条件必须用
while循环包裹await(),防止虚假唤醒 -
signal()必须在修改状态后、释放锁前调用,否则唤醒的线程可能立即再次 await - 不要在持有锁时执行耗时操作(如 I/O),否则会阻塞其他线程
示例片段(简化):
public void put(E e) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待有空位
}
items[putIndex] = e;
if (++putIndex == items.length) putIndex = 0;
++count;
notEmpty.signal(); // 唤醒一个消费者
} finally {
lock.unlock();
}
}
和 Object.wait/notify 的本质区别
Condition 是面向对象的等待队列抽象:
- 每个
Condition关联一个明确语义(如“非空”),可读性高 - 一个锁可绑定多个
Condition,实现多路等待/唤醒 -
await()会自动释放锁,被唤醒后自动重新获取锁,无需手动管理 - 支持带超时的
awaitNanos(long)、中断响应等高级能力
而 Object.wait() 是粗粒度的,所有等待者挤在一个队列里,唤醒靠运气,不适合复杂协作场景。

















