blockingqueue是java并发包中专为生产者-消费者模型设计的线程安全阻塞队列;需据场景选arrayblockingqueue(有界、控资源)或linkedblockingqueue(高吞吐、慎防oom),正确使用take/put、避免依赖size()、配合poison pill实现优雅关闭。

BlockingQueue 是 Java 并发包中专为生产者-消费者模型设计的线程安全队列,它天然支持阻塞操作,能有效简化多线程协作逻辑。用对了,代码简洁又健壮;用错了,容易卡死、丢数据或性能骤降。
选对实现类:根据场景决定用 ArrayBlockingQueue 还是 LinkedBlockingQueue
ArrayBlockingQueue 是有界数组实现,创建时必须指定容量,线程安全且内存占用可控,适合对资源使用敏感、需严格限流的场景(比如订单处理上限 1000 条)。LinkedBlockingQueue 默认无界(实际是 Integer.MAX_VALUE),底层链表结构,吞吐量通常更高,但若生产远快于消费,可能引发 OOM。
- 明确最大积压量 → 优先选 ArrayBlockingQueue
- 消费能力稳定、追求吞吐 → 可用 LinkedBlockingQueue,但建议显式传入 capacity 参数避免无限扩张
- 需要高并发、低延迟且允许少量丢失 → 考虑 Disruptor(非 BlockingQueue,但更高效)
正确使用 take() 和 put():别在循环里裸调用导致假死
take() 在队列空时会一直阻塞,put() 在队列满时也会一直阻塞。如果消费者线程在 shutdown 后仍调用 take(),或生产者在 close 流程中还调用 put(),就可能永久挂起。
- 消费者退出前应配合使用 interrupt(),并在 catch InterruptedException 后主动退出循环
- 生产者可改用 offer(e, timeout, unit) 替代 put(),超时返回 false,便于做降级或告警
- 必要时用 drainTo(collection) 批量取数,减少锁竞争
避免“虚假唤醒”和状态误判:不要依赖队列 size() 做业务判断
BlockingQueue 的 size() 返回的是瞬时快照,多线程下不可靠。例如判断 if (queue.size() > 0) 再 take(),中间可能已被其他线程取走,导致 take() 阻塞——这不是 bug,而是设计使然。
- 业务逻辑中禁止用 size() == 0 代替 isEmpty() 或作为是否继续消费的依据
- 需要感知队列状态变化时,可用 peek() 尝试获取头元素(不移除),返回 null 表示空,比 size() 更可靠
- 如需精确监控积压量,应在生产/消费关键路径加原子计数器,而非依赖队列自身 size()
关闭与清理:确保生产者停止后消费者能自然退出
没有内置的“优雅关闭”协议,需自行约定信号机制。常见做法是向队列中插入特殊结束标记(POISON PILL),消费者收到后退出循环。
- 定义唯一哨兵对象(如 private static final Object POISON = new Object())
- 所有生产者完成任务后,各发一个 POISON 到队列(数量 = 消费者线程数)
- 消费者 take() 后判断 if (e == POISON) break,然后释放资源、return
- 避免用 null 作哨兵——BlockingQueue 不允许 null 元素,会抛 NullPointerException










