常见的阻塞队列有哪几种?怎么选?
结论先行:java.util.concurrent 提供 7 种阻塞队列,选型只需回答三个问题:要不要有界、要不要排序、要不要按时间延迟。线程池里最常用的是 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 三种。
1. 七种队列对照
| 队列 | 底层结构 | 是否有界 | 排序 | 典型场景 |
|---|---|---|---|---|
ArrayBlockingQueue | 数组 + 单锁双条件 | 有界(必填容量) | FIFO | 线程池、稳定可控的缓冲 |
LinkedBlockingQueue | 链表 + 双锁 | 可选(默认 Integer.MAX_VALUE) | FIFO | 吞吐优先,必须显式给容量 |
SynchronousQueue | 无存储,配对传递 | 容量 0 | 无 | CachedThreadPool、直接交接 |
PriorityBlockingQueue | 二叉堆数组 | 无界 | 按 Comparator | 优先级任务调度 |
DelayQueue | 优先队列 + 延迟 | 无界 | 到期时间 | 定时任务、订单超时关闭 |
LinkedTransferQueue | 链表 | 无界 | FIFO | 高吞吐传递,transfer() 等消费者 |
LinkedBlockingDeque | 双向链表 | 可选 | FIFO/LIFO | 工作窃取式双端取任务 |
2. 四种典型语义
ArrayBlockingQueue(3) SynchronousQueue
┌───┬───┬───┐ put ──┐
│ a │ b │ c │ 满了 put 阻塞 ├──▶ 必须有人正等着 take
└───┴───┴───┘ 空了 take 阻塞 take ─┘ 否则 put 阻塞(无缓冲)
PriorityBlockingQueue DelayQueue
take 永远返回最小元素 take 只返回"到期"的元素
(无界,生产过快会 OOM) 未到期元素即使队首也要继续等
3. 常用写法
// 1. 有界数组队列:线程池首选,容量固定、内存可控
BlockingQueue<Task> q1 = new ArrayBlockingQueue<>(1000);
// 2. 链表队列:吞吐更高,但务必显式指定容量
BlockingQueue<Task> q2 = new LinkedBlockingQueue<>(1000);
// 3. 直接交接:不存储,生产者必须等到消费者
BlockingQueue<Task> q3 = new SynchronousQueue<>();
// 4. 延迟队列:元素实现 Delayed,到期才能被取出
public class OrderTimeout implements Delayed {
private final long expireAt; // 绝对时间戳(毫秒)
public long getDelay(TimeUnit unit) {
return unit.convert(expireAt - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
public int compareTo(Delayed o) {
return Long.compare(this.expireAt,
((OrderTimeout) o).expireAt);
}
}
DelayQueue<OrderTimeout> delayQ = new DelayQueue<>();
delayQ.put(new OrderTimeout(System.currentTimeMillis() + 30 * 60_000L));
OrderTimeout expired = delayQ.take(); // 阻塞到最早的元素到期
线程池里换队列,行为会完全不同:
| 队列 | 效果 |
|---|---|
ArrayBlockingQueue(n) | 满了就触发拒绝策略,背压明确 |
LinkedBlockingQueue() 无参 | maximumPoolSize 永远用不上,任务无限堆积 → OOM |
SynchronousQueue | 没有缓冲,来一个任务就必须有线程接,接不住就扩容或拒绝 |
4. 选型决策
需要按优先级/到期时间取任务吗?
├── 需要 → DelayQueue(按时间)/ PriorityBlockingQueue(按优先级)
└── 不需要 → 需要缓冲吗?
├── 不需要缓冲,希望直接交给线程 → SynchronousQueue
└── 需要缓冲 → 容量能预估吗?
├── 能 → ArrayBlockingQueue(内存连续、边界清晰)
└── 不能 → LinkedBlockingQueue(但必须设上限)
5. 常见追问
追问一:ArrayBlockingQueue 和 LinkedBlockingQueue 的性能差异?
ArrayBlockingQueue 一把锁管进出,LinkedBlockingQueue 用 putLock/takeLock 两把锁分离生产和消费,并发更高;但链表要为每个节点分配对象,GC 压力大。量级不大时优先数组,因为内存可控。
追问二:SynchronousQueue 为什么给 CachedThreadPool 用?
CachedThreadPool 的语义是"来任务就找空闲线程,没有就新建"。SynchronousQueue 容量为 0,offer 失败即说明没有空闲线程,于是扩容——这正好实现了"按需扩容"。
追问三:PriorityBlockingQueue 有什么坑?
无界。生产速度长期高于消费速度时,堆会一直涨到 OOM;另外它不保证同优先级元素的顺序,且元素必须可比较(Comparable 或传 Comparator)。
追问四:DelayQueue 的 take() 是怎么等到期的?
队首元素的 getDelay() 大于 0 时,take() 会 awaitNanos(delay) 精确等待,而不是空转轮询。ScheduledThreadPoolExecutor 内部的 DelayedWorkQueue 是同一套思路的定制版。
小结
阻塞队列的选择本质是三个开关:有没有界、排不排序、延不延迟。工程上最稳的组合是"ArrayBlockingQueue + 明确容量 + 明确的拒绝策略"——边界清楚,出问题也可观测。