BlockingQueue是Java并发包中专为生产者-消费者设计的线程安全阻塞队列,满时生产者阻塞、空时消费者阻塞;常用实现包括ArrayBlockingQueue(有界数组)、LinkedBlockingQueue(链表,高吞吐)、PriorityBlockingQueue(优先级)和SynchronousQueue(不存储,手递手传递)。

BlockingQueue 是 Java 并发包(java.util.concurrent)中一个关键的线程安全队列接口,专为**生产者-消费者场景**设计。它的核心特点是:当队列满时,生产者线程会阻塞等待空间;当队列空时,消费者线程会阻塞等待元素。不用手动加锁或调用 wait/notify,就能安全高效地实现线程间协作。
常用实现类及适用场景
Java 提供了多个 BlockingQueue 的实现,选对实现很重要:
- ArrayBlockingQueue:基于数组、有界队列,创建时必须指定容量。适合对内存使用有明确限制、且希望避免无节制增长的场景。
-
LinkedBlockingQueue:基于链表,默认无界(实际容量为
Integer.MAX_VALUE),也可指定容量。吞吐量通常高于 ArrayBlockingQueue,适合高并发、元素数量波动大的情况。 -
PriorityBlockingQueue:支持优先级排序的无界队列,元素需实现
Comparable或传入Comparator。注意:它不保证完全按优先级“即时”消费,但出队总遵循优先级顺序。 -
SynchronousQueue:不存储元素的特殊队列。每个插入操作必须等待另一个线程的移除操作,反之亦然。本质是“手递手”传递,常用于工作窃取、线程池的直接交接(如
Executors.newCachedThreadPool()内部使用)。
核心方法:阻塞 vs 超时 vs 抛异常
BlockingQueue 定义了三套行为不同的方法,对应不同容错需求:
-
阻塞式:
put(E e)(满则阻塞)、take()(空则阻塞)——最常用,逻辑简洁,适合不希望丢数据、能接受等待的场景。 -
超时式:
offer(E e, long timeout, TimeUnit unit)、poll(long timeout, TimeUnit unit)——指定时间内尝试操作,超时返回false或null。适合需要控制等待上限、避免无限挂起的系统(如实时性要求高的服务)。 -
抛异常式:
add(E e)、remove()、element()——队列满或空时直接抛IllegalStateException或NoSuchElementException。一般不推荐在多线程中直接使用,因无法应对并发下的状态瞬变。
典型生产者-消费者代码结构
用 LinkedBlockingQueue 实现一个简单但完整的例子:
立即学习“Java免费学习笔记(深入)”;
定义队列:
BlockingQueue
生产者(模拟生成任务):
new Thread(() -> {
for (int i = 0; i
try {
queue.put("task-" + i);
System.out.println("Produced: task-" + i);
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
消费者(模拟处理任务):
new Thread(() -> {
while (true) {
try {
String task = queue.take(); // 空则阻塞
System.out.println("Consumed: " + task);
TimeUnit.MILLISECONDS.sleep(200); // 模拟处理耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
使用注意事项
几个容易踩坑的点:
- 所有操作天然线程安全,但遍历(如
iterator())不保证强一致性——可能看到部分更新或重复元素,仅适合调试,不要依赖其结果做业务判断。 - 队列为空时
take()会一直阻塞,如果消费者线程需优雅退出,建议配合poll(timeout, unit)+ 循环条件,或用中断机制响应 shutdown。 -
SynchronousQueue容量为 0,size()永远返回 0,不能用它判断是否有待处理任务。 - 如果元素本身不是线程安全的(比如自定义对象含可变字段),BlockingQueue 只保证“引用”的存取安全,对象内部状态仍需自行同步。
基本上就这些。BlockingQueue 把复杂的线程协调封装得非常干净,用好它,比手写 wait/notify 稳定得多,也比自己加锁更不容易出错。











