Java 集合 Queue 与并发容器:ArrayDeque、PriorityQueue、BlockingQueue、CopyOnWriteArrayList 一篇讲清
前面我们聊了 ArrayList、LinkedList、HashMap、ConcurrentHashMap 这些高频集合。
这一篇继续补上 Java 集合里很重要的一条线:
Queue 队列体系 + 常见并发容器
很多人平时写业务代码时,可能 List 和 Map 用得最多,但一到线程池、生产者消费者、任务调度、Top K、延迟任务这些场景,就绕不开队列。
这篇文章就把 Queue、Deque、ArrayDeque、PriorityQueue、BlockingQueue、CopyOnWriteArrayList 这些常见知识点整理清楚。
一、Queue 是什么?
Queue 是队列接口,特点通常是:
先进先出 FIFO
也就是先放进去的元素,先被取出来。
入队:A -> B -> C
出队:A -> B -> C
常见方法:
offer(); // 入队
poll(); // 出队并删除
peek(); // 查看队头但不删除
适合场景:
任务排队 消息缓冲 生产者消费者 广度优先搜索 BFS
二、offer、poll、peek 和 add、remove、element 有什么区别?
这两组方法都能操作队列,但失败时表现不同。
| 操作 | 推荐方法 | 失败时 |
|---|---|---|
| 入队 | offer() | 返回 false |
| 出队 | poll() | 返回 null |
| 查看队头 | peek() | 返回 null |
另一组方法:
| 操作 | 方法 | 失败时 |
|---|---|---|
| 入队 | add() | 可能抛异常 |
| 出队 | remove() | 可能抛异常 |
| 查看队头 | element() | 可能抛异常 |
实际开发中更常用:
offer()
poll()
peek()
因为它们失败时表现更温和,不会动不动抛异常。
三、Deque 是什么?
Deque 是双端队列。
它既可以从队头操作,也可以从队尾操作。
队头 <- [A, B, C] -> 队尾
常见方法:
offerFirst();
offerLast();
pollFirst();
pollLast();
peekFirst();
peekLast();
所以 Deque 既能当队列用,也能当栈用。
四、ArrayDeque 是什么?
ArrayDeque 是基于数组实现的双端队列。
它通常比 LinkedList 更适合作为队列或栈使用。
当栈使用:
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.pop();
当队列使用:
Deque<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.poll();
面试可以这样说:
如果只是做栈或队列,通常优先用 ArrayDeque,而不是 Stack 或 LinkedList。
五、为什么不推荐使用 Stack?
Stack 是比较老的类,继承自 Vector,方法带同步,设计较旧。
现在更推荐:
Deque<Integer> stack = new ArrayDeque<>();
原因:
API 更现代 性能通常更好 语义更清晰
所以面试中如果问“Java 里怎么实现栈”,可以说优先用 ArrayDeque。
六、PriorityQueue 是什么?
PriorityQueue 是优先队列。
它不是普通的先进先出,而是每次取出“优先级最高”的元素。
默认是小顶堆:
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 1
默认情况下,数字越小,优先级越高。
七、PriorityQueue 底层是什么?
PriorityQueue 底层是堆,默认是小顶堆。
堆可以用数组表示,逻辑上像一棵完全二叉树:
1
/ \
3 2
特点:
| 操作 | 时间复杂度 |
|---|---|
| 查看堆顶 | O(1) |
| 插入元素 | O(logN) |
| 删除堆顶 | O(logN) |
它适合 Top K、任务优先级调度、找第 K 大/第 K 小等场景。
八、PriorityQueue 如何实现大顶堆?
默认是小顶堆,如果想让大的元素优先出来,可以传比较器:
PriorityQueue<Integer> queue = new PriorityQueue<>((a, b) -> b - a);
不过更安全的写法是:
PriorityQueue<Integer> queue = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
因为 b - a 在极端情况下可能整数溢出。
九、PriorityQueue 适合什么场景?
常见场景:
Top K 问题 合并多个有序链表 任务优先级调度 找第 K 大 / 第 K 小元素
比如找数组中最大的 K 个数,可以维护一个大小为 K 的小顶堆。
思路:
堆中始终保留当前最大的 K 个元素 如果新元素比堆顶大,就替换堆顶 最后堆里就是最大的 K 个数
流程图:

十、什么是 BlockingQueue?
BlockingQueue 是阻塞队列。
它和普通队列最大的区别是:
队列为空时,取元素的线程可以阻塞等待 队列满时,放元素的线程可以阻塞等待
这非常适合生产者-消费者模型。
生产者 -> BlockingQueue -> 消费者
流程图:

十一、BlockingQueue 常见实现有哪些?
常见实现:
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
DelayQueue
SynchronousQueue
LinkedTransferQueue
实习和初中级面试重点掌握这些:
| 实现 | 特点 |
|---|---|
| ArrayBlockingQueue | 数组,有界 |
| LinkedBlockingQueue | 链表,通常可有界 |
| PriorityBlockingQueue | 优先级阻塞队列 |
| DelayQueue | 延迟队列 |
| SynchronousQueue | 不存储元素,直接交接 |
线程池里也经常会见到阻塞队列。
十二、ArrayBlockingQueue 和 LinkedBlockingQueue 区别?
| 对比 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 底层 | 数组 | 链表 |
| 是否有界 | 必须指定容量 | 可指定容量 |
| 锁 | 通常一把锁 | 读写可分离 |
| 内存 | 预先分配数组 | 节点动态创建 |
简单记:
ArrayBlockingQueue:数组有界阻塞队列
LinkedBlockingQueue:链表阻塞队列
ArrayBlockingQueue 更稳定可控;LinkedBlockingQueue 如果不指定容量,可能堆积很多任务,所以在线程池里要特别注意。
十三、SynchronousQueue 是什么?
SynchronousQueue 很特殊,它不存储元素。
每个放入操作必须等待一个取出操作。
可以理解为:
生产者直接把任务交给消费者
如果没有消费者接收,生产者就阻塞。
流程图:

它常用于某些线程池策略,比如希望任务来了就直接交给线程处理,而不是堆积在队列里。
十四、DelayQueue 是什么?
DelayQueue 是延迟队列。
元素只有到期后才能被取出。
适合:
订单超时取消 缓存延迟清理 定时任务 重试任务
比如订单 30 分钟未支付自动取消,就可以用延迟队列思想。
不过真实项目里,也可能使用 MQ 延迟消息或 Redis ZSet 实现延迟任务。
十五、CopyOnWriteArrayList 是什么?
CopyOnWriteArrayList 是线程安全的 List。
它的核心思想是:
读不加锁 写时复制
写操作时,不直接修改原数组,而是复制一份新数组,在新数组上修改,然后替换引用。
旧数组:A B C 写入 D 新数组:A B C D 引用指向新数组
流程图:

十六、CopyOnWriteArrayList 适合什么场景?
适合:
读多写少 遍历频繁 数据量不大
比如:
系统配置监听器列表 白名单列表 事件订阅者列表
不适合写很多的场景,因为每次写都要复制数组,成本较高。
十七、为什么 CopyOnWriteArrayList 遍历时不会抛 ConcurrentModificationException?
因为它遍历的是某一时刻的数组快照。
写操作会复制新数组,不会影响当前正在遍历的旧数组。
所以它不会像普通 ArrayList 那样触发 fail-fast。
但它也有一个特点:
遍历时不一定能看到最新写入的数据
这是读写一致性和并发性能之间的取舍。
十八、Collections.synchronizedList 是什么?
它可以把普通 List 包装成线程安全 List。
List<String> list = Collections.synchronizedList(new ArrayList<>());
它的原理大致是给方法加同步锁。
但是遍历时仍然要手动加锁:
synchronized (list) {
for (String s : list) {
// 遍历
}
}
否则仍然可能有并发问题。
十九、Collections 和 Collection 有什么区别?
这是很经典的小问题。
| 名称 | 含义 |
|---|---|
| Collection | 集合顶层接口之一 |
| Collections | 集合工具类 |
Collection 是接口:
List、Set、Queue 都属于 Collection 体系
Collections 是工具类,提供很多静态方法:
Collections.sort(list);
Collections.reverse(list);
Collections.unmodifiableList(list);
Collections.synchronizedList(list);
别把这两个混了。
二十、这一组怎么串起来讲?
你可以这样回答:
Java 集合除了 List、Set、Map,还有 Queue 队列体系。 Queue 通常表示先进先出,Deque 是双端队列,ArrayDeque 常用来实现栈和队列。 PriorityQueue 底层是堆,适合 Top K 和优先级调度。 BlockingQueue 是阻塞队列,适合生产者-消费者模型,也是线程池的重要组成部分。 并发场景下,CopyOnWriteArrayList 适合读多写少,因为它读不加锁,写时复制。 如果只是简单包装线程安全集合,可以用 Collections.synchronizedList,但遍历时仍要注意加锁。
总体流程图

总结
这一组可以按下面这条线来记:
Queue:先进先出 Deque:双端队列,可当栈用
ArrayDeque:常用栈/队列实现,优先于 Stack
PriorityQueue:底层堆,适合 Top K
BlockingQueue:阻塞队列,适合生产者-消费者
ArrayBlockingQueue:数组有界阻塞队列
LinkedBlockingQueue:链表阻塞队列
SynchronousQueue:不存元素,生产者消费者直接交接
DelayQueue:延迟队列
CopyOnWriteArrayList:读多写少,写时复制
Collections:集合工具类
Collection:集合接口
这一组重点背:ArrayDeque、PriorityQueue 底层堆、BlockingQueue、ArrayBlockingQueue vs LinkedBlockingQueue、SynchronousQueue、DelayQueue、CopyOnWriteArrayList、Collections 和 Collection 区别。下一组就可以进入 Java 并发,从线程、进程、创建线程、线程状态开始。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)