blockingqueue 是 java 并发包中线程安全的阻塞队列接口,适用于生产者-消费者模型,如任务分发、日志缓冲等;不适用于普通容器,不可存 null,选型需据容量与性能需求而定。

BlockingQueue 是什么,什么时候该用它
BlockingQueue 是 Java 并发包(java.util.concurrent)中一个线程安全的队列接口,核心特点是:当队列为空时,take() 会阻塞直到有元素;当队列为满时(如有界队列),put() 会阻塞直到有空位。它天然适合「生产者-消费者」模型,比如任务分发、日志缓冲、异步批处理等场景。
- 不适合做普通容器(如替代
ArrayList),因为所有操作都有同步开销 - 不能存
null,否则抛NullPointerException - 多数实现(如
ArrayBlockingQueue、LinkedBlockingQueue)不保证公平性,除非显式启用(如new ArrayBlockingQueue<>(10, true))
选哪个实现?ArrayBlockingQueue vs LinkedBlockingQueue vs SynchronousQueue
选择取决于容量约束、性能预期和线程协作方式:
-
ArrayBlockingQueue:有界、数组实现、内存紧凑、吞吐稳定- 构造必须指定容量:
new ArrayBlockingQueue<>(1024) - 内部用单个
ReentrantLock(可选公平锁),入队出队共用一把锁,高并发下可能成瓶颈
- 构造必须指定容量:
-
LinkedBlockingQueue:默认无界(Integer.MAX_VALUE)、链表实现、入出队分离锁立即学习“Java免费学习笔记(深入)”;
- 无参构造实际是“伪无界”,慎用于生产环境(OOM 风险)
-
put()和take()使用不同锁,吞吐通常高于ArrayBlockingQueue
-
SynchronousQueue:无内部容量,“手递手”传递,适合高响应任务交接-
put()必须等待另一个线程调用take()才返回,反之亦然 - 不适合缓冲,适合构建线程池的直接交接策略(如
Executors.newCachedThreadPool()底层就用它)
-
怎么避免 put/take 意外阻塞或死锁
阻塞本身是设计特性,但容易在以下情况引发问题:
- 在持有其他锁时调用
put()或take(),可能造成锁顺序死锁 - 使用无界队列(如未设限的
LinkedBlockingQueue)+ 生产过快 → 内存耗尽 - 忽略中断:
take()被中断会抛InterruptedException,若没正确处理(比如没恢复中断状态),上层线程可能无法响应 shutdown
建议做法:
- 优先用带超时的版本:
offer(e, 5, TimeUnit.SECONDS)、poll(5, TimeUnit.SECONDS),失败可降级或告警 - 若需响应中断,捕获
InterruptedException后应调用Thread.currentThread().interrupt() - 在线程池场景中,避免把
BlockingQueue当作长期积压任务的“保险库”,应配合拒绝策略(如AbortPolicy、CallerRunsPolicy)
一个最小可行的生产者-消费者示例
// 共享队列
BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
<p>// 生产者线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
queue.put("task-" + i); // 阻塞直到入队成功
System.out.println("produced: task-" + i);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}).start();</p><p>// 消费者线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
String task = queue.take(); // 阻塞直到取到任务
System.out.println("consumed: " + task);
Thread.sleep(100); // 模拟处理耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}).start();</p>
注意:这个例子没做 shutdown 协调,真实场景需用 CountDownLatch 或 ExecutorService 管理生命周期。另外,LinkedBlockingQueue 的容量限制(100)在这里只是防爆,不是流控依据——真正控制速率得靠生产端节制或使用 Semaphore。
真正难的不是调用 put 和 take,而是判断何时该阻塞、何时该超时、以及阻塞期间线程是否还该响应外部信号。这些决策藏在业务语义里,不在 API 文档里。










