第50篇:阻塞队列与并发容器(2026版)
📌 系列导航 :《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第49篇:ConcurrentHashMap原理 |
➡️ 下一篇:第51篇:线程生命周期与创建方式
文章目录
-
- 第50篇:阻塞队列与并发容器(2026版)
-
- [🗺️ 本文阅读地图(3 分钟速览)](#🗺️ 本文阅读地图(3 分钟速览))
- 一、核心知识点
- 二、生活类比:从"奶茶店柜台"到"智能餐厅"
- 三、阻塞队列核心机制
-
- [3.1 什么是 BlockingQueue?](#3.1 什么是 BlockingQueue?)
- [3.2 核心 API 速记](#3.2 核心 API 速记)
- [3.3 五大实现类对比](#3.3 五大实现类对比)
- [3.4 源码片段:ArrayBlockingQueue 的 put/take(锁+条件)](#3.4 源码片段:ArrayBlockingQueue 的 put/take(锁+条件))
- [3.5 源码片段:LinkedBlockingQueue 的 put/take(双锁设计)](#3.5 源码片段:LinkedBlockingQueue 的 put/take(双锁设计))
- [四、CopyOnWriteArrayList:读多写少的并发 List](#四、CopyOnWriteArrayList:读多写少的并发 List)
-
- [4.1 核心思想](#4.1 核心思想)
- [4.2 源码片段](#4.2 源码片段)
- [4.3 优缺点与适用场景](#4.3 优缺点与适用场景)
- [五、ConcurrentLinkedQueue:CAS 无锁非阻塞队列](#五、ConcurrentLinkedQueue:CAS 无锁非阻塞队列)
-
- [5.1 核心思想](#5.1 核心思想)
- [5.2 核心特点](#5.2 核心特点)
- [5.3 核心方法](#5.3 核心方法)
- 六、线程池中的阻塞队列选型
- 七、生产级避坑清单
- 八、面试高频考点
- 面试官追问陷阱(加分题)
- 九、练习题
- [📊 你的学习进度](#📊 你的学习进度)
- [👉 下一篇文章预告](#👉 下一篇文章预告)
🗺️ 本文阅读地图(3 分钟速览)
第45~49篇拿下了 HashMap 和 ConcurrentHashMap,本篇是集合框架源码系列收官之作 ,聚焦并发场景下的队列与 List 容器:
| 模块 | 核心问题 | 一句话回答 |
|---|---|---|
| BlockingQueue 是什么 | 阻塞队列解决了什么问题? | 生产者-消费者模型的线程安全桥梁,队列满/空时自动阻塞 |
| Array vs Linked vs Sync | 三种阻塞队列怎么选? | 固定容量用 Array,高吞吐用 Linked,线程池传引用用 Sync |
| CopyOnWriteArrayList | 并发读多写少用什么 List? | 读无锁、写时复制,读多写少场景的王者 |
| ConcurrentLinkedQueue | 非阻塞队列是什么? | CAS 无锁实现,适合高并发、不要求阻塞的场景 |
| 线程池中的应用 | 阻塞队列在线程池里起什么作用? | 任务缓冲,核心参数之一 |
一、核心知识点
阻塞队列(BlockingQueue):
- 定义 :支持阻塞入队/出队的线程安全队列,队列满时
put()阻塞生产者,队列空时take()阻塞消费者 - 五大实现 :
ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue、DelayQueue - 核心 API :
put(e)/take()(阻塞)、offer(e)/poll()(非阻塞)、offer(e, timeout)/poll(timeout)(超时)
并发容器:
- CopyOnWriteArrayList :写时复制,读无锁写加锁,读多写少场景首选
- ConcurrentLinkedQueue:CAS 无锁非阻塞队列,高并发任务队列首选
二、生活类比:从"奶茶店柜台"到"智能餐厅"
BlockingQueue 就像一个奶茶店柜台:
- 柜台容量有限(有界队列),做好的奶茶放在柜台上。
- 顾客(消费者)来取,柜台空了就等着(
take()阻塞)。 - 店员(生产者)做好了放上去,柜台满了就等一下再放(
put()阻塞)。
SynchronousQueue 是"手递手"柜台:柜台没有放置空间,店员做好一杯奶茶,必须直接递到顾客手里,两边同时在场才能完成交接。
CopyOnWriteArrayList 像"复印店" :多人同时阅读一份报纸(读操作无锁)。有人要改内容时,先复印一份新报纸在副本上改,改完再替换原版(写时复制)。正在读旧版的人不受影响。

三、阻塞队列核心机制
3.1 什么是 BlockingQueue?
BlockingQueue 是 java.util.concurrent 包下的线程安全队列接口,专为生产者-消费者模型设计。
核心特性:
- 线程安全:所有实现类保证多线程并发操作的安全性
- 阻塞插入 :队列满时,
put(e)阻塞生产者线程 - 阻塞移除 :队列空时,
take()阻塞消费者线程 - 超时支持 :
offer(e, timeout, unit)和poll(timeout, unit)支持超时阻塞 - 容量限制:分为有界队列(固定容量)和无界队列(理论无限容量)
3.2 核心 API 速记
| 操作类型 | 队列满/空时行为 | 插入方法 | 移除方法 | 检查方法 |
|---|---|---|---|---|
| 抛出异常 | 立即抛异常 | add(e) |
remove() |
element() |
| 返回特殊值 | 返回 false/null | offer(e) |
poll() |
peek() |
| 阻塞 | 阻塞直到成功 | put(e) |
take() |
--- |
| 超时 | 超时返回 false/null | offer(e, time, unit) |
poll(time, unit) |
--- |
3.3 五大实现类对比
| 实现类 | 底层结构 | 容量 | 核心特点 | 适用场景 |
|---|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界(必须指定) | 公平锁可选,内存紧凑 | 固定容量生产者-消费者 |
| LinkedBlockingQueue | 链表 | 可选有界(默认无界) | 生产者和消费者用独立锁,吞吐量高 | 高吞吐任务队列 |
| SynchronousQueue | 无存储 | 容量为 0 | 手递手,生产等消费 | 线程池(CachedThreadPool) |
| PriorityBlockingQueue | 堆(数组) | 无界 | 按优先级排序 | 优先级任务调度 |
| DelayQueue | 堆 + 延迟 | 无界 | 元素需实现 Delayed,到期才能取出 |
定时任务、订单超时关闭 |
🔑 关键区别:
- Array vs Linked :Array 用一把锁,Linked 用两把锁 (
takeLock+putLock),吞吐量更高 - 默认容量 :
new LinkedBlockingQueue()是无界(Integer.MAX_VALUE),极易 OOM! - SynchronousQueue:容量为 0,不存储元素,生产者直接等待消费者接收
3.4 源码片段:ArrayBlockingQueue 的 put/take(锁+条件)
java
// ArrayBlockingQueue 核心:一把锁 + 两个条件
public class ArrayBlockingQueue<E> {
final ReentrantLock lock; // 唯一锁
private final Condition notEmpty; // 队列非空条件
private final Condition notFull; // 队列未满条件
public void put(E e) throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == items.length) // 队列满 → 等待
notFull.await();
enqueue(e);
} finally { lock.unlock(); }
}
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == 0) // 队列空 → 等待
notEmpty.await();
return dequeue();
} finally { lock.unlock(); }
}
}
3.5 源码片段:LinkedBlockingQueue 的 put/take(双锁设计)
java
// LinkedBlockingQueue 核心:两把锁,分离生产与消费
public class LinkedBlockingQueue<E> {
private final ReentrantLock takeLock = new ReentrantLock(); // 消费者锁
private final ReentrantLock putLock = new ReentrantLock(); // 生产者锁
private final Condition notEmpty = takeLock.newCondition();
private final Condition notFull = putLock.newCondition();
public void put(E e) throws InterruptedException {
putLock.lockInterruptibly(); // 只锁生产者
try {
while (count == capacity) notFull.await();
enqueue(e);
} finally { putLock.unlock(); }
}
public E take() throws InterruptedException {
takeLock.lockInterruptibly(); // 只锁消费者
try {
while (count == 0) notEmpty.await();
return dequeue();
} finally { takeLock.unlock(); }
}
}
💡 双锁设计 :生产者和消费者可同时操作,互不阻塞,吞吐量更高。
四、CopyOnWriteArrayList:读多写少的并发 List
4.1 核心思想
CopyOnWriteArrayList 是线程安全的 ArrayList 变体,核心思想是写时复制(Copy-On-Write):
写操作 :加锁复制一份新数组,修改完成后替换原数组
读操作:完全无锁,直接读当前数组
4.2 源码片段
java
public class CopyOnWriteArrayList<E> {
private transient volatile Object[] array; // volatile 保证可见性
// 读操作:无锁!
public E get(int index) {
return get(getArray(), index); // 直接读,不加锁
}
// 写操作:加锁 + 复制
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 1. 加锁
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // 2. 复制
newElements[len] = e; // 3. 修改副本
setArray(newElements); // 4. 替换引用
return true;
} finally {
lock.unlock();
}
}
}
4.3 优缺点与适用场景
| 维度 | 说明 |
|---|---|
| ✅ 优点 | 读操作无锁,读多写少 场景性能极高;迭代器不抛 ConcurrentModificationException |
| ❌ 缺点 | 每次写都复制整个数组,写操作代价高 ;数据弱一致性(读可能读到旧数据) |
| 适用场景 | 读多写少:配置列表、黑白名单、缓存数据等 |
| 不适用场景 | 写操作频繁的场景(如实时计数器) |
五、ConcurrentLinkedQueue:CAS 无锁非阻塞队列
5.1 核心思想
ConcurrentLinkedQueue 是无界、非阻塞、线程安全 的 FIFO 队列,基于链表 + CAS 实现。
与 BlockingQueue 的本质区别:
BlockingQueue:用锁 实现阻塞(put/take可阻塞线程)ConcurrentLinkedQueue:用 CAS 实现非阻塞(offer/poll立即返回,不阻塞)
5.2 核心特点
| 特性 | 说明 |
|---|---|
| 线程安全 | 多线程并发插入/删除不会导致数据不一致 |
| 非阻塞 | 使用 CAS 操作,避免锁竞争导致的线程阻塞 |
| 无界 | 可动态扩展,理论无限容量(受内存限制) |
| 高性能 | 无锁设计,高并发下性能优于阻塞队列 |
5.3 核心方法
java
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("task1"); // 插入,立即返回 true
queue.add("task2"); // 插入,失败抛异常
String task = queue.poll(); // 移除并返回头元素,空返回 null
String task = queue.peek(); // 返回头元素不移除,空返回 null
六、线程池中的阻塞队列选型
| 线程池 | 默认阻塞队列 | 特点 |
|---|---|---|
FixedThreadPool |
LinkedBlockingQueue |
无界队列,任务量需可控,否则 OOM |
CachedThreadPool |
SynchronousQueue |
不存任务,直接交给线程,无任务时销毁线程 |
SingleThreadExecutor |
LinkedBlockingQueue |
同 Fixed,单线程 |
ScheduledThreadPool |
DelayedWorkQueue |
延迟队列,支持定时/周期任务 |
七、生产级避坑清单
text
✅ 阻塞队列与并发容器使用规范
1. LinkedBlockingQueue 无参构造默认无界(Integer.MAX_VALUE)→ 务必指定容量,否则 OOM
2. ArrayBlockingQueue 必须指定容量,创建时明确队列大小
3. SynchronousQueue 不存储元素,offer() 无消费者立即返回 false,必须用 put()
4. CopyOnWriteArrayList 写操作频繁时性能极差 → 仅用于读多写少场景
5. ConcurrentLinkedQueue 的 size() 是 O(n) 遍历 → 不要频繁调用
6. 阻塞队列的 take() 会阻塞线程 → 确保有对应的生产者,否则线程永久阻塞
八、面试高频考点
Q1:BlockingQueue 的核心方法有哪些?区别是什么?
四类方法:① 抛异常(
add/remove/element);② 返回特殊值(offer/poll/peek);③ 阻塞(put/take);④ 超时(offer(time)/poll(time))。put/take是阻塞队列的核心方法。
Q2:ArrayBlockingQueue 和 LinkedBlockingQueue 的区别?
Array 基于数组,必须指定容量 ,用一把锁,内存紧凑。Linked 基于链表,可选容量 (默认无界),生产者和消费者用独立锁,吞吐量更高。高并发场景 Linked 更优,但需注意无界队列可能 OOM。
Q3:SynchronousQueue 的作用?
容量为 0 的阻塞队列,不存储元素,生产者插入必须等待消费者接收。常用于
Executors.newCachedThreadPool(),实现任务直接传递给线程。
Q4:CopyOnWriteArrayList 的原理和适用场景?
写时复制:写操作加锁复制整个数组,修改后替换原数组;读操作完全无锁。适用于读多写少场景(如配置列表、黑白名单)。写频繁时性能极差。
Q5:ConcurrentLinkedQueue 和 BlockingQueue 的区别?
ConcurrentLinkedQueue是非阻塞 队列,基于 CAS 无锁实现;BlockingQueue是阻塞队列,基于锁 + Condition 实现。前者适合高并发任务队列,后者适合生产者-消费者模型。
面试官追问陷阱(加分题)
追问1 :"new LinkedBlockingQueue() 不指定容量会怎样?"
👉 默认容量是
Integer.MAX_VALUE,近似无界。如果生产者速度持续快于消费者,队列会无限膨胀,最终 OOM 。生产环境务必指定容量。
追问2 :"CopyOnWriteArrayList 的迭代器会抛 ConcurrentModificationException 吗?"
👉 不会 。迭代器基于创建时的数组快照,遍历期间不感知后续修改,因此不会抛异常。代价是读不到最新数据(弱一致性)。
追问3 :"ConcurrentLinkedQueue 的 size() 为什么不准确?"
👉
size()需要遍历整个链表累加计数,时间复杂度 O(n),且遍历过程中可能有并发修改,结果不精确。高并发场景下慎用。
九、练习题
-
源码推导 :
ArrayBlockingQueue和LinkedBlockingQueue的锁机制有什么区别?各有什么优缺点? -
场景设计 :某系统需要维护一份读频率极高、几乎不修改的敏感词列表,选用什么并发容器最合适?
-
代码分析:下面的代码有什么问题?
javaLinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(); for (int i = 0; i < 1000000; i++) { queue.put("task" + i); }
📊 你的学习进度
- 当前:第50篇 / 共108篇 · 进阶篇:集合框架源码解析(第45~50篇)
- ✅ 已完成:基础篇44篇 + 第45~50篇(集合框架源码系列收官!)
- 📖 正在学:第50篇
- ⏳ 待学习:第51~108篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
👉 下一篇文章预告
🚀 下一篇:《第51篇:线程生命周期与创建方式》
内容简介:线程的 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)、状态流转图、创建线程的 4 种方式(Thread、Runnable、Callable、线程池)。
👉 第45~50篇集合框架源码系列正式收官!下一阶段开启多线程与高并发专题!
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!