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

📅 2026/7/4 4:21:49 👁️ 阅读次数 📝 编程学习
《Java 100 天进阶之路》第50篇:阻塞队列与并发容器(2026版)

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

📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第49篇:ConcurrentHashMap原理 |
➡️ 下一篇:第51篇:线程生命周期与创建方式


文章目录

    • 第50篇:阻塞队列与并发容器(2026版)
      • 🗺️ 本文阅读地图(3 分钟速览)
      • 一、核心知识点
      • 二、生活类比:从“奶茶店柜台”到“智能餐厅”
      • 三、阻塞队列核心机制
        • 3.1 什么是 BlockingQueue?
        • 3.2 核心 API 速记
        • 3.3 五大实现类对比
        • 3.4 源码片段:ArrayBlockingQueue 的 put/take(锁+条件)
        • 3.5 源码片段:LinkedBlockingQueue 的 put/take(双锁设计)
      • 四、CopyOnWriteArrayList:读多写少的并发 List
        • 4.1 核心思想
        • 4.2 源码片段
        • 4.3 优缺点与适用场景
      • 五、ConcurrentLinkedQueue:CAS 无锁非阻塞队列
        • 5.1 核心思想
        • 5.2 核心特点
        • 5.3 核心方法
      • 六、线程池中的阻塞队列选型
      • 七、生产级避坑清单
      • 八、面试高频考点
      • 面试官追问陷阱(加分题)
      • 九、练习题
    • 📊 你的学习进度
    • 👉 下一篇文章预告

🗺️ 本文阅读地图(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/nulloffer(e)poll()peek()
阻塞阻塞直到成功put(e)take()
超时超时返回 false/nulloffer(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(锁+条件)
// ArrayBlockingQueue 核心:一把锁 + 两个条件publicclassArrayBlockingQueue<E>{finalReentrantLocklock;// 唯一锁privatefinalConditionnotEmpty;// 队列非空条件privatefinalConditionnotFull;// 队列未满条件publicvoidput(Ee)throwsInterruptedException{lock.lockInterruptibly();try{while(count==items.length)// 队列满 → 等待notFull.await();enqueue(e);}finally{lock.unlock();}}publicEtake()throwsInterruptedException{lock.lockInterruptibly();try{while(count==0)// 队列空 → 等待notEmpty.await();returndequeue();}finally{lock.unlock();}}}
3.5 源码片段:LinkedBlockingQueue 的 put/take(双锁设计)
// LinkedBlockingQueue 核心:两把锁,分离生产与消费publicclassLinkedBlockingQueue<E>{privatefinalReentrantLocktakeLock=newReentrantLock();// 消费者锁privatefinalReentrantLockputLock=newReentrantLock();// 生产者锁privatefinalConditionnotEmpty=takeLock.newCondition();privatefinalConditionnotFull=putLock.newCondition();publicvoidput(Ee)throwsInterruptedException{putLock.lockInterruptibly();// 只锁生产者try{while(count==capacity)notFull.await();enqueue(e);}finally{putLock.unlock();}}publicEtake()throwsInterruptedException{takeLock.lockInterruptibly();// 只锁消费者try{while(count==0)notEmpty.await();returndequeue();}finally{takeLock.unlock();}}}

💡双锁设计:生产者和消费者可同时操作,互不阻塞,吞吐量更高。


四、CopyOnWriteArrayList:读多写少的并发 List

4.1 核心思想

CopyOnWriteArrayList是线程安全的ArrayList变体,核心思想是写时复制(Copy-On-Write)

写操作:加锁复制一份新数组,修改完成后替换原数组
读操作:完全无锁,直接读当前数组

4.2 源码片段
publicclassCopyOnWriteArrayList<E>{privatetransientvolatileObject[]array;// volatile 保证可见性// 读操作:无锁!publicEget(intindex){returnget(getArray(),index);// 直接读,不加锁}// 写操作:加锁 + 复制publicbooleanadd(Ee){finalReentrantLocklock=this.lock;lock.lock();// 1. 加锁try{Object[]elements=getArray();intlen=elements.length;Object[]newElements=Arrays.copyOf(elements,len+1);// 2. 复制newElements[len]=e;// 3. 修改副本setArray(newElements);// 4. 替换引用returntrue;}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 核心方法
ConcurrentLinkedQueue<String>queue=newConcurrentLinkedQueue<>();queue.offer("task1");// 插入,立即返回 truequeue.add("task2");// 插入,失败抛异常Stringtask=queue.poll();// 移除并返回头元素,空返回 nullStringtask=queue.peek();// 返回头元素不移除,空返回 null

六、线程池中的阻塞队列选型

线程池默认阻塞队列特点
FixedThreadPoolLinkedBlockingQueue无界队列,任务量需可控,否则 OOM
CachedThreadPoolSynchronousQueue不存任务,直接交给线程,无任务时销毁线程
SingleThreadExecutorLinkedBlockingQueue同 Fixed,单线程
ScheduledThreadPoolDelayedWorkQueue延迟队列,支持定时/周期任务

七、生产级避坑清单

✅ 阻塞队列与并发容器使用规范 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. 代码分析:下面的代码有什么问题?

    LinkedBlockingQueue<String>queue=newLinkedBlockingQueue<>();for(inti=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!
👉 点击关注我,更新后第一时间收到推送!