Lock配合Condition实现生产者消费者模型的核心是精准控制线程等待与唤醒,通过notFull和notEmpty两个Condition分组等待、定向通知,避免虚假唤醒和无效竞争,且需用while循环判断条件、finally释放锁。

Java 中用 Lock 配合 Condition 实现生产者消费者模型,核心在于**精准控制线程等待与唤醒**,避免无差别 notify 导致的虚假唤醒或唤醒错对象。相比 synchronized + wait/notify,它更灵活、语义更清晰,也更易维护。
一、为什么 Condition 比 wait/notify 更“优雅”
Condition 是 Lock 的配套等待队列机制,一个 Lock 可绑定多个 Condition 实例,从而实现**按条件分组等待**:
- 生产者只在“缓冲区满”时等待,唤醒时只通知消费者;
- 消费者只在“缓冲区空”时等待,唤醒时只通知生产者;
- 不会出现
notifyAll()唤醒所有线程再竞争锁的浪费; - 支持带超时的
awaitNanos、awaitUntil,便于做有界等待和容错。
二、关键结构:Lock + 两个 Condition
用一个 ReentrantLock 保护共享资源(如阻塞队列),并创建两个 Condition:
-
notFull:供生产者 await,当缓冲区满时等待,生产后 signal; -
notEmpty:供消费者 await,当缓冲区空时等待,消费后 signal。
注意:所有 await() 和 signal() 必须在持有 lock 的前提下调用,否则抛 IllegalMonitorStateException。
立即学习“Java免费学习笔记(深入)”;
三、典型实现(固定容量缓冲区)
以下是一个简洁、线程安全、可直接运行的核心片段:
public class BoundedBuffer<T> {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Object[] items;
private int takeIndex, putIndex, count;
public BoundedBuffer(int capacity) {
this.items = new Object[capacity];
}
public void put(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待“不满”条件
}
items[putIndex] = x;
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(); // 等待“不空”条件
}
Object x = items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
--count;
notFull.signal(); // 唤醒至少一个等待生产的线程
} finally {
lock.unlock();
}
return (T) x;
}
}
四、实用建议与避坑点
- 永远用 while 而非 if 判断条件:防止虚假唤醒(spurious wakeup)导致状态不一致;
- signal() 优于 signalAll():除非明确需要唤醒所有等待者(如广播场景),否则单次唤醒更高效;
- lock 必须在 finally 中释放:确保异常时不会死锁;
- 避免嵌套 await:一个线程不应在未释放 lock 的情况下多次 await,容易引发死锁或逻辑混乱;
- 若需公平调度,构造
ReentrantLock(true),但会带来性能开销,一般默认非公平即可。


















