前面我们聊了 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 并发,从线程、进程、创建线程、线程状态开始。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐