《Java 100 天进阶之路》第50篇:阻塞队列与并发容器(2026版)

第50篇:阻塞队列与并发容器(2026版)

📌 系列导航《Java 100 天进阶之路》完整目录 |

⬅️ 上一篇:第49篇:ConcurrentHashMap原理 |

➡️ 下一篇:第51篇:线程生命周期与创建方式


文章目录


🗺️ 本文阅读地图(3 分钟速览)

第45~49篇拿下了 HashMap 和 ConcurrentHashMap,本篇是集合框架源码系列收官之作 ,聚焦并发场景下的队列与 List 容器

模块 核心问题 一句话回答
BlockingQueue 是什么 阻塞队列解决了什么问题? 生产者-消费者模型的线程安全桥梁,队列满/空时自动阻塞
Array vs Linked vs Sync 三种阻塞队列怎么选? 固定容量用 Array,高吞吐用 Linked,线程池传引用用 Sync
CopyOnWriteArrayList 并发读多写少用什么 List? 读无锁、写时复制,读多写少场景的王者
ConcurrentLinkedQueue 非阻塞队列是什么? CAS 无锁实现,适合高并发、不要求阻塞的场景
线程池中的应用 阻塞队列在线程池里起什么作用? 任务缓冲,核心参数之一

一、核心知识点

阻塞队列(BlockingQueue)

  • 定义 :支持阻塞入队/出队的线程安全队列,队列满时 put() 阻塞生产者,队列空时 take() 阻塞消费者
  • 五大实现ArrayBlockingQueueLinkedBlockingQueueSynchronousQueuePriorityBlockingQueueDelayQueue
  • 核心 APIput(e) / take()(阻塞)、offer(e) / poll()(非阻塞)、offer(e, timeout) / poll(timeout)(超时)

并发容器

  • CopyOnWriteArrayList :写时复制,读无锁写加锁,读多写少场景首选
  • ConcurrentLinkedQueue:CAS 无锁非阻塞队列,高并发任务队列首选

二、生活类比:从"奶茶店柜台"到"智能餐厅"

BlockingQueue 就像一个奶茶店柜台

  • 柜台容量有限(有界队列),做好的奶茶放在柜台上。
  • 顾客(消费者)来取,柜台空了就等着(take() 阻塞)。
  • 店员(生产者)做好了放上去,柜台满了就等一下再放(put() 阻塞)。

SynchronousQueue 是"手递手"柜台:柜台没有放置空间,店员做好一杯奶茶,必须直接递到顾客手里,两边同时在场才能完成交接。

CopyOnWriteArrayList 像"复印店" :多人同时阅读一份报纸(读操作无锁)。有人要改内容时,先复印一份新报纸在副本上改,改完再替换原版(写时复制)。正在读旧版的人不受影响。


三、阻塞队列核心机制

3.1 什么是 BlockingQueue?

BlockingQueuejava.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 :"ConcurrentLinkedQueuesize() 为什么不准确?"

👉 size() 需要遍历整个链表累加计数,时间复杂度 O(n),且遍历过程中可能有并发修改,结果不精确。高并发场景下慎用


九、练习题

  1. 源码推导ArrayBlockingQueueLinkedBlockingQueue 的锁机制有什么区别?各有什么优缺点?

  2. 场景设计 :某系统需要维护一份读频率极高、几乎不修改的敏感词列表,选用什么并发容器最合适?

  3. 代码分析:下面的代码有什么问题?

    java 复制代码
    LinkedBlockingQueue<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!

👉 点击关注我,更新后第一时间收到推送!

相关推荐
ai_coder_ai1 小时前
编写自动化脚本,在自己后端服务中使用Open Api进行设备相关操作
java·运维·自动化
硕风和炜2 小时前
【LeetCode: 2492. 两个城市间路径的最小分数 + DFS】
java·算法·leetcode·深度优先·dfs·bfs·并查集
格子软件2 小时前
2026年GEO贴牌代理:分布式多级分账状态机源码深度解构
java·vue.js·分布式·vue·geo
我是一颗柠檬3 小时前
【Java项目技术亮点】加权轮询负载均衡算法
java·算法·负载均衡
灯厂码农3 小时前
C语言动态内存分配完全指南(malloc、calloc、realloc、free)
java·c语言·算法
梦梦代码精4 小时前
电商系统不是技术堆叠:LikeShop如何用分层Hold住复杂业务?
java·docker·代码规范
负责的蛋挞4 小时前
异步HttpModule的实现方式
java·服务器·前端
AC赳赳老秦4 小时前
防火墙规则批量配置实战:OpenClaw 自动生成模板、批量下发与合规性校验全解析
java·开发语言·人工智能·python·github·php·openclaw
Tian_Hang4 小时前
Eclipse Ditto 物模型相关代码
java·运维·服务器·ide·eureka·eclipse