blockingqueue 是实现生产者消费者模型最简、最可靠方式,内部已封装同步逻辑;推荐使用 arrayblockingqueue、linkedblockingqueue 或 synchronousqueue,注意用 put/take 而非 add/remove,并用 while 循环检查条件避免虚假唤醒。

用 BlockingQueue 实现最简生产者消费者
Java 标准库提供了线程安全的阻塞队列,这是实现生产者消费者模型最直接、最可靠的方式。不需要手动加锁或 wait/notify,BlockingQueue 内部已封装好所有同步逻辑。
常见选择有:ArrayBlockingQueue(固定容量、基于数组)、LinkedBlockingQueue(可选容量、基于链表)、SynchronousQueue(不缓存元素,适合“手递手”交接)。
关键行为:
- put() 在队列满时阻塞,take() 在队列空时阻塞
- 不要用 add() 或 remove(),它们在失败时抛异常而非阻塞
- 若需超时控制,改用 offer(e, timeout, unit) 和 poll(timeout, unit)
避免在 synchronized 块里调用 wait()/notifyAll() 的典型错误
手写 wait/notify 容易出错,尤其在多个条件共存或虚假唤醒场景下。比如只用一个 notify() 可能唤醒错误类型的等待线程;未用 while 循环检查条件会导致跳过状态判断。
正确写法必须是:
立即学习“Java免费学习笔记(深入)”;
while (queue.isEmpty()) {
wait();
}而不是 if (queue.isEmpty()) { wait(); }。因为 wait() 返回后,条件可能已被其他线程改变,必须重新校验。
另外注意:
- wait() 和 notifyAll() 必须在同一个对象锁内调用
- 调用 notify() 无法保证唤醒哪个线程,高并发下易导致饥饿
- notifyAll() 开销略大但更安全,除非你能严格控制等待线程类型和数量
用 ReentrantLock + Condition 实现多条件等待
当需要区分“队列满”和“队列空”两种等待逻辑(例如想单独唤醒生产者或消费者),ReentrantLock 配合两个 Condition 更清晰可控。
示例片段:
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
<p>// 消费者取数据
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
Object item = queue.poll();
notFull.signal(); // 通知生产者可以继续放了
} finally {
lock.unlock();
}要点:
- 每个 Condition 对应一类等待原因,避免混用
- signal() 比 signalAll() 更轻量,但仅适用于单线程等待场景
- 必须在 try-finally 中释放锁,否则极易死锁
别忽略关闭信号与资源清理
真实系统中,生产者消费者常需优雅停机。如果只靠中断线程,可能卡在 take() 或 await() 上,导致无法退出。
推荐做法:
- 生产者端发送特殊结束标记(如 null 或自定义 POISON_PILL 对象)
- 消费者收到后自行终止循环,而不是依赖外部中断
- 若使用线程池,记得调用 shutdown() + awaitTermination()
- 避免在 finally 块中调用 interrupt() 并吞掉 InterruptedException,这会掩盖中断意图
最容易被忽略的是:多个消费者共享一个队列时,一个消费者收到结束信号,其他消费者仍可能长期阻塞——必须确保所有消费者都能感知到终止条件,通常靠队列中放入与消费者数量相等的结束标记来解决。










