你在大纲里已经列了 ConcurrentHashMapConcurrentLinkedQueueBlockingQueue 等,但工程上更常见的问题是:

  • 我该选哪一种?
  • 迭代器为什么“看起来不一致”?
  • 为什么会 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. 选型建议(工程版)

  • 你需要“有界 + 背压”:ArrayBlockingQueueLinkedBlockingQueue(cap)
  • 你需要“零排队、直接移交”:SynchronousQueue
  • 你需要“定时/延迟”:DelayQueue(或 ScheduledThreadPoolExecutor
  • 你需要“并发 + 有序”:ConcurrentSkipListMap/Set
  • 你需要“读多写少的列表”:CopyOnWriteArrayList/Set

7. 常见事故与排查方向

  • OOM:
    • 使用了无界队列(LinkedBlockingQueue 默认无界、PriorityBlockingQueue 无界)
    • 消费端变慢导致积压
  • 延迟抖动:
    • 锁竞争(单锁队列、高竞争的 synchronized 容器)
    • GC 压力(链表节点大量分配)
  • “遍历不一致”误判为 bug:
    • 弱一致迭代器的预期行为

8. 小结

并发容器不是“线程安全的集合”这么简单:

  • 它们在一致性语义、性能、内存开销上都有明确取舍。
  • 工程选型优先回答三件事:是否需要有界、是否需要阻塞、是否需要有序。

面试题 / Checklist

  • 什么是弱一致迭代器?为什么并发容器通常不抛 CME?
  • CopyOnWriteArrayList 的写放大体现在哪里?适合/不适合什么场景?
  • ConcurrentSkipListMap 的核心价值是什么?相比 CHM 的取舍点?
  • ConcurrentLinkedQueue 为什么适合高并发?它的“无界”风险如何治理?
  • ArrayBlockingQueue vs LinkedBlockingQueue 的锁模型差异与适用场景?
  • SynchronousQueue 的语义是什么?它对线程池行为有什么影响?