并发包下的阻塞队列(BlockingQueue)有哪些常见的实现类?
在 Java 的并发包(java.util.concurrent)中,BlockingQueue(阻塞队列)是解决并发生产者-消费者问题的核心组件。它支持在队列为空时阻塞获取元素的线程,在队列满时阻塞添加元素的线程。
常见的 BlockingQueue 实现类有以下 7 种,它们在底层数据结构、有界/无界、锁机制和适用场景上各有不同:
1. ArrayBlockingQueue(基于数组的有界阻塞队列)
- 底层结构:数组。
- 是否有界:严格有界。必须在创建时指定容量(capacity),且创建后无法修改。
- 锁机制:使用一把全局
ReentrantLock锁(出队和入队共用一把锁),因此并发度相对较低。 - 特点:
- 按照 FIFO(先进先出)原则对元素进行排序。
- 支持公平锁和非公平锁(默认非公平)。
- 内存分配连续,不会频繁产生 Node 对象,对 GC 友好。
- 适用场景:需要严格控制内存大小,且并发量不是极高的场景。
2. LinkedBlockingQueue(基于链表的阻塞队列)
- 底层结构:单向链表。
- 是否有界:可选有界。默认容量是
Integer.MAX_VALUE(通常被视为无界),也可以在创建时指定容量。 - 锁机制:分离锁(两把锁)。入队使用
putLock,出队使用takeLock,因此入队和出队可以并发执行,吞吐量通常高于ArrayBlockingQueue。 - 特点:
- 按照 FIFO 原则排序。
- 如果是默认容量,在生产者速度远大于消费者速度时,存在耗尽内存(OOM)的风险。
- 适用场景:高并发的生产者-消费者场景。(最常用的实现类,
Executors.newFixedThreadPool()就使用了它)。
3. SynchronousQueue(不存储元素的同步队列)
- 底层结构:无缓冲。内部不存储任何元素。
- 是否有界:无界/零容量。
- 锁机制:底层通过非阻塞算法(CAS)或特定结构的锁实现。
- 特点:
- 容量为 0。每一个
put操作必须等待一个take操作,否则无法继续添加元素;反之亦然。 - 它更像是一个“传递者”,直接将任务从生产者移交给消费者。
- 支持公平(基于队列)和非公平(基于栈)模式。
- 容量为 0。每一个
- 适用场景:传递性场景,即生产者和消费者处理速度匹配,或者需要快速响应的场景。(
Executors.newCachedThreadPool()使用了它)。
4. PriorityBlockingQueue(支持优先级的无界阻塞队列)
- 底层结构:数组实现的二叉小顶堆。
- 是否有界:无界(容量不够时会自动扩容,直到
MAX_ARRAY_SIZE)。 - 锁机制:一把全局锁。
- 特点:
- 不保证 FIFO。元素出队顺序由其自然顺序(实现
Comparable接口)或提供的Comparator决定。 - 因为是无界队列,
put操作永远不会阻塞,只有当内存耗尽时才会抛出OutOfMemoryError;take操作在队列为空时会阻塞。
- 不保证 FIFO。元素出队顺序由其自然顺序(实现
- 适用场景:任务需要区分优先级执行的场景(如 VIP 用户优先处理、紧急警报优先发送)。
5. DelayQueue(支持延迟获取元素的无界阻塞队列)
- 底层结构:内部封装了
PriorityQueue。 - 是否有界:无界。
- 特点:
- 队列中的元素必须实现
Delayed接口(该接口继承了Comparable)。 - 只有当元素的延迟时间(Delay)耗尽时,消费者才能从队列中取走该元素。
- 内部按照延迟时间的长短进行优先级排序。
- 队列中的元素必须实现
- 适用场景:
- 缓存系统的设计(清理过期缓存)。
- 定时任务调度(类似
Timer或ScheduledThreadPoolExecutor的底层机制)。 - 订单超时未支付自动取消。
6. LinkedTransferQueue(基于链表的无界传输队列)
- 底层结构:单向链表。
- 是否有界:无界。
- 特点:
- 实现了
TransferQueue接口。可以看作是ConcurrentLinkedQueue、SynchronousQueue和LinkedBlockingQueue的超集。 - 特有方法
transfer(E e):如果当前有消费者正在等待接收元素,生产者会直接把元素传给消费者;如果没有,生产者会将元素放在队列尾部,并阻塞等到该元素被消费者消费后才返回。 - 大量使用了无锁(CAS)操作,性能通常比
LinkedBlockingQueue更高。
- 实现了
- 适用场景:要求极高吞吐量且生产者需要知道消息已被消费的场景。
7. LinkedBlockingDeque(基于双向链表的阻塞双端队列)
- 底层结构:双向链表。
- 是否有界:可选有界(默认
Integer.MAX_VALUE)。 - 特点:
- 实现了
Deque接口,支持从队列的两端插入和移出元素(addFirst,addLast,takeFirst,takeLast等)。 - 由于是双端操作,多线程并发时,一端入队一端出队可以减少竞争。
- 实现了
- 适用场景:“工作窃取”(Work-Stealing)模式。例如 Fork/Join 框架中,每个线程有自己的双端队列,当自己的任务做完后,可以从其他线程的队列尾部“窃取”任务执行。
💡 总结与选型建议(对比表)
| 实现类 | 底层数据结构 | 有界/无界 | 锁机制 | 适用场景 |
|---|---|---|---|---|
| ArrayBlockingQueue | 数组 | 严格有界 | 单锁 | 需控制内存,对吞吐量要求非极致的场景 |
| LinkedBlockingQueue | 单向链表 | 可选(默认无界) | 双锁(分离锁) | 高吞吐量的常规生产者-消费者场景 |
| SynchronousQueue | 无缓冲(队列/栈) | 零容量 | 无锁/单锁 | 需快速交接任务,如 CachedThreadPool |
| PriorityBlockingQueue | 堆(优先队列) | 无界 | 单锁 | 任务有优先级区分的场景 |
| DelayQueue | 优先队列 | 无界 | 单锁 | 延迟执行、定时任务、超时处理 |
| LinkedTransferQueue | 单向链表 | 无界 | CAS无锁机制 | 极高吞吐量,且需确认消息被消费 |
| LinkedBlockingDeque | 双向链表 | 可选(默认无界) | 单锁 | 工作窃取模式,需两端操作的场景 |
面试高频考点:ArrayBlockingQueue 和 LinkedBlockingQueue 的区别(结构、锁机制、内存占用)。实际开发中,强烈建议在使用 LinkedBlockingQueue 时手动指定容量,防止生产过快导致 OOM。