选对BlockingQueue实现类、设好容量、正确调用阻塞方法是核心:ArrayBlockingQueue适合固定容量高吞吐场景,LinkedBlockingQueue需显式设容量防OOM,SynchronousQueue用于无缓冲接力,PriorityBlockingQueue支持优先级但不保证公平;生产者依可靠性选put()、offer(e)或offer(e,timeout),消费者按实时性选take()、poll(timeout)或poll();须处理中断重置、退出时drainTo清空、避免强引用内存泄漏。

Java 里用 BlockingQueue 实现生产者消费者,根本不用手写 wait/notify,核心是选对实现类、设好容量、正确调用阻塞方法。
怎么选合适的 BlockingQueue 实现类
不同场景下性能和语义差异很大,不能只图方便全用 LinkedBlockingQueue:
-
ArrayBlockingQueue:固定容量、基于数组、线程安全、吞吐量高;适合容量明确且不想动态扩容的场景(比如日志缓冲区限 1024 条) -
LinkedBlockingQueue:默认无界(实际是Integer.MAX_VALUE),但建议显式指定容量,否则 OOM 风险高;适合吞吐量中等、允许一定内存占用的场景 -
SynchronousQueue:不存储元素,每个put()必须等待对应take(),适合纯“接力”型任务分发(如线程池的直接交接模式) -
PriorityBlockingQueue:无界、支持优先级排序;注意元素必须可比较,且不保证公平性(可能饿死低优先级任务)
生产者怎么避免 put() 卡死或丢数据
盲目调用 put() 在有界队列满时会无限阻塞,而 offer() 又可能丢任务——得按需选策略:
- 想严格保序、不丢数据、能接受延迟:用
put(),但务必确保消费者跟得上,否则队列持续满导致生产者线程堆积 - 想控制响应时间:用
offer(e, timeout, TimeUnit),超时返回false,之后可降级处理(如落盘重试、告警) - 想快速失败不阻塞:用
offer(e),立即返回布尔值,适合实时性要求高、允许少量丢失的场景(如监控指标采样)
消费者怎么避免 take() 阻塞太久或空转
take() 永远阻塞,poll() 立即返回 null,都不是万能解——关键看业务是否允许“空等”或“轮询”:
立即学习“Java免费学习笔记(深入)”;
- 后台常驻消费者(如消息处理线程):用
take()最省资源,无任务时线程挂起,唤醒即处理 - 需要带超时做状态检查(如心跳、健康检测):用
poll(timeout, unit),超时后可执行清理或上报逻辑 - 绝对禁止阻塞的上下文(如 Netty EventLoop):只能用
poll(),但必须配合循环+短休眠(如Thread.sleep(1)),否则 CPU 100%
常见坑:中断、关闭与内存泄漏
很多人忽略线程中断和队列生命周期管理,导致应用停不掉或对象无法回收:
- 被
put()或take()阻塞的线程,收到interrupt()会抛InterruptedException并清中断状态——必须在 catch 后重新设置中断标志:Thread.currentThread().interrupt(); - 消费者线程退出前,如果队列还有残留元素,应调用
drainTo(Collection)清空并处理,否则任务永久丢失 -
LinkedBlockingQueue的节点是强引用,若存的是大对象或未及时消费,GC 不会回收,容易引发老年代溢出
真正难的不是写通逻辑,而是根据吞吐、延迟、可靠性、资源约束去权衡容量、超时、中断响应和清空策略——这些细节没压测过,上线就容易卡在边界 case 上。

















