并发容器与队列——选型、弱一致迭代与 BlockingQueue 家族
你在大纲里已经列了 ConcurrentHashMap、ConcurrentLinkedQueue、BlockingQueue 等,但工程上更常见的问题是:
- 我该选哪一种?
- 迭代器为什么“看起来不一致”?
- 为什么会 OOM/卡死/延迟抖动?
本篇用“选型 + 坑点”的角度补齐。
1. 并发容器的一个核心语义:弱一致(weakly consistent)
很多并发容器的迭代器(iterator)是弱一致:
- 迭代期间允许并发修改
- 不抛
ConcurrentModificationException - 但你不能指望它是某个时间点的强一致快照
工程含义:
- 用并发容器做统计/遍历时,要接受“近似一致”,或自行做快照(拷贝/版本化)。
2. CopyOnWriteArrayList/Set:读多写少的利器
2.1 适用场景
- 读远多于写(配置、白名单、监听器列表)
- 允许写入成本高(写时复制)
2.2 关键特性
- 写操作会复制底层数组(写放大)
- 迭代器是快照语义:迭代看到的是创建迭代器时的数组
2.3 大坑
- 写频繁会非常慢 + 内存抖动大
- 元素很大、数组很大时复制成本极高
3. ConcurrentSkipListMap/Set:并发 + 有序
当你需要:
- 并发安全
- 并且需要有序遍历/范围查询(
subMap/headMap/tailMap)
优先考虑 SkipList。
取舍:
- 相比
ConcurrentHashMap,常数更大,纯 key->value 查询可能更慢 - 但它提供了“有序能力”,这是 CHM 做不到的
4. ConcurrentLinkedQueue:无锁队列(高并发下很香)
典型特点:
- 非阻塞(lock-free)
offer/poll使用 CAS
工程注意:
- 它是无界队列:如果生产者远快于消费者,会积压占内存
- 如果你需要“背压/阻塞”,直接用
BlockingQueue
5. BlockingQueue 家族:线程池与生产者-消费者的地基
5.1 ArrayBlockingQueue(有界、数组、单锁)
- 优点:强有界、内存可控
- 缺点:单锁竞争下吞吐可能受限
- 适用:更想要可控延迟/可控内存
5.2 LinkedBlockingQueue(可选有界、链表、双锁)
- 优点:put/take 分离,吞吐通常更好
- 缺点:节点对象分配更多;如果无界会 OOM
5.3 SynchronousQueue(零容量,直接移交)
- 适用:不想排队,想用“扩线程”应对突发(配合合理 max)
- 易错:max 过大 → 线程爆炸;max 太小 → 拒绝风暴
5.4 PriorityBlockingQueue(优先级,无界)
- 适用:任务有优先级
- 大坑:默认无界,仍要考虑内存风险
5.5 DelayQueue(延迟队列)
- 适用:定时任务、延迟重试、超时检查
- 关键点:元素必须实现
Delayed
5.6 LinkedTransferQueue(高吞吐移交队列)
- 特点:支持 transfer(生产者可以等待消费者接走)
- 在高并发场景中常被用于高吞吐任务移交
6. 选型建议(工程版)
- 你需要“有界 + 背压”:
ArrayBlockingQueue或LinkedBlockingQueue(cap) - 你需要“零排队、直接移交”:
SynchronousQueue - 你需要“定时/延迟”:
DelayQueue(或ScheduledThreadPoolExecutor) - 你需要“并发 + 有序”:
ConcurrentSkipListMap/Set - 你需要“读多写少的列表”:
CopyOnWriteArrayList/Set
7. 常见事故与排查方向
- OOM:
- 使用了无界队列(LinkedBlockingQueue 默认无界、PriorityBlockingQueue 无界)
- 消费端变慢导致积压
- 延迟抖动:
- 锁竞争(单锁队列、高竞争的 synchronized 容器)
- GC 压力(链表节点大量分配)
- “遍历不一致”误判为 bug:
- 弱一致迭代器的预期行为
8. 小结
并发容器不是“线程安全的集合”这么简单:
- 它们在一致性语义、性能、内存开销上都有明确取舍。
- 工程选型优先回答三件事:是否需要有界、是否需要阻塞、是否需要有序。
面试题 / Checklist
- 什么是弱一致迭代器?为什么并发容器通常不抛 CME?
CopyOnWriteArrayList的写放大体现在哪里?适合/不适合什么场景?ConcurrentSkipListMap的核心价值是什么?相比 CHM 的取舍点?ConcurrentLinkedQueue为什么适合高并发?它的“无界”风险如何治理?ArrayBlockingQueuevsLinkedBlockingQueue的锁模型差异与适用场景?SynchronousQueue的语义是什么?它对线程池行为有什么影响?
