Java Queue / Deque / PriorityQueue 与阻塞队列
更新时间:2026-09-21。集合总览里的 List/Map/Set 见 ArrayList/LinkedList 与 HashMap 原理,本篇专讲队列族。
一、两个接口:Queue 与 Deque
Queue 单端,队尾进、队头出。两组方法,满/空时行为不同:
| 操作 | 抛异常 | 返回特殊值 | 阻塞 |
|---|---|---|---|
| 入队 | add | offer | put |
| 出队 | remove | poll | take |
| 看队头 | element | peek | — |
Deque(双端队列)两端都能进出,既可当队列也可当栈。
二、实现类怎么选
| 实现 | 底层 | 特点 |
|---|---|---|
| ArrayDeque | 可扩容循环数组 | 队头队尾 O(1) 摊销,首选的栈与双端队列,不能放 null |
| LinkedList | 双向链表 | 同时实现 List/Deque,随机访问慢、节点开销大,现已少选 |
| PriorityQueue | 二叉堆 | 出队按优先级,不是 FIFO,非线程安全 |
| ArrayBlockingQueue | 定长数组+锁 | 有界阻塞队列 |
| LinkedBlockingQueue | 链表+锁 | 默认 Integer.MAX_VALUE(近乎无界,慎用) |
| SynchronousQueue | 不存元素 | 直接交接,CachedThreadPool 用它 |
| DelayQueue | 堆 | 元素到期才能 take |
| PriorityBlockingQueue | 堆 | 支持优先级的无界阻塞队列 |
三、栈:用 ArrayDeque,别用 Stack
java.util.Stack 继承 Vector、方法全 synchronized,古老且效率低。官方推荐:
java
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2);
int top = stack.pop(); // 2
int see = stack.peek(); // 1四、PriorityQueue 的要点
- 队头永远是「最小」元素(按自然顺序或传入的 Comparator),遍历顺序不保证有序,只有反复 poll 才得到升序序列。
- 元素必须可比较:实现 Comparable 或构造时给 Comparator,否则
ClassCastException。比较器写法见 Comparable/Comparator。 - 适合 Top-K / 任务优先级调度;要「最大堆」传
Comparator.reverseOrder()。
五、阻塞队列与线程池
阻塞队列是生产者-消费者的核心:put 满则等待、take 空则等待,线程安全。
java
BlockingQueue<Task> q = new ArrayBlockingQueue<>(100);
q.put(task); // 队列满时阻塞,天然背压
Task t = q.take(); // 队列空时阻塞队列选择直接影响线程池行为,详见 线程池参数怎么设:有界队列防 OOM、SynchronousQueue 追求直接交接、LinkedBlockingQueue 不显式设容量等于埋了无界队列的雷。
六、常见坑
- 用 Stack 类:换 ArrayDeque。
- PriorityQueue 遍历期望有序:只有 poll 顺序有序,要整体有序请 poll 完或用 sorted List。
- PriorityQueue 放不可比较对象又没给 Comparator:ClassCastException。
- ArrayDeque 插入 null:不允许(无法与 poll 返回 null 区分)。
- LinkedBlockingQueue 用默认容量:近乎无界,生产快于消费会堆爆内存,务必显式设容量。
总结
单端用 Queue、两端操作用 Deque,非并发场景栈和双端队列默认选 ArrayDeque(别用继承 Vector 的 Stack),需要 List 能力才考虑 LinkedList;PriorityQueue 基于堆、只有 poll 才按优先级出队、元素必须可比较;并发生产者消费者用 BlockingQueue,put 满则阻塞 take 空则等待、天然背压,线程池里优先有界的 ArrayBlockingQueue,慎用默认无界的 LinkedBlockingQueue 和不存元素的 SynchronousQueue。