并发容器的本质不是“线程安全”四个字,而是:

  • 在不同读写比例下,怎么用不同的数据结构把性能与一致性做到可控

这篇按“选型 -> 原理 -> 坑 -> 排查”的结构讲:

  • ConcurrentHashMap(CHM)
  • CopyOnWriteArrayList
  • BlockingQueue(阻塞队列)

1. ConcurrentHashMap:你至少要讲清 3 个点

1.1 为什么 HashMap 线程不安全

  • 扩容/链表转换过程中可能出现数据丢失、死循环等问题(历史版本更明显)

1.2 CHM 怎么做到高并发

你可以按版本讲:

  • JDK7:分段锁(Segment)
  • JDK8:CAS + synchronized(桶级锁)+ 红黑树

工程上你只需要把结论讲清:

  • 锁粒度从“全表”降到“桶/节点”,并在大部分情况下走 CAS 快路径

1.3 put/get 的性能特点

  • get 大多无锁
  • put 在冲突桶上加锁(synchronized)

2. CHM 的常见坑

  • 把 CHM 当成“强一致 map”:并发下 size/遍历是弱一致
  • computeIfAbsent 里做慢操作:可能导致桶锁长时间占用
  • 把大对象塞进去:内存压力与 GC 压力上升

排查建议:

  • 如果发现某个 key 热点更新,考虑分段 key 或引入 LongAdder 聚合

3. CopyOnWriteArrayList:读多写少的利器

核心机制:

  • 写操作时复制一份新数组,修改后再替换引用
  • 读操作不加锁,直接读当前数组引用

适用场景:

  • 读多写少(例如白名单、配置快照、监听器列表)

不适用:

  • 写频繁(复制数组成本很高)

4. BlockingQueue:线程池与生产消费的核心组件

常见实现:

  • ArrayBlockingQueue:有界、数组、一个锁(公平可选)
  • LinkedBlockingQueue:可有界也可无界(无界风险大)
  • SynchronousQueue:不存储,直接交接
  • DelayQueue:延迟任务

工程选型:

  • 需要背压:优先有界队列
  • 低延迟短任务:可考虑 SynchronousQueue(配合合理 max)

5. 阻塞队列常见坑

  • 无界队列堆积:最终 OOM 或延迟爆炸
  • 把阻塞队列当缓存:阻塞队列不是缓存(更像协调组件)
  • 生产快于消费:需要背压、限流、或扩容消费者

6. 线上排查:容器导致的性能问题怎么看

  • CPU 高:可能是热点 key 自旋/CAS 重试,或 computeIfAbsent 慢逻辑
  • RT 抖:可能是桶锁竞争、写放大、或队列堆积
  • OOM:可能是无界队列、或 map/列表塞了大对象

7. 面试追问 Q&A

  • Q:CHM 为什么 get 快?
    • A:读大多无锁,依赖 volatile/内存语义保证可见性。
  • Q:CopyOnWrite 为什么适合读多写少?
    • A:读无锁,写复制数组,写成本高。
  • Q:阻塞队列和普通队列有什么本质区别?
    • A:阻塞队列提供阻塞/唤醒语义,用于线程协作与背压。

8. 面试表达(30 秒讲清楚)

  • CHM 在 JDK8 主要靠 CAS + 桶级锁提高并发度,get 大多无锁,put 在冲突桶上加锁。
  • CopyOnWrite 写时复制,读无锁,适合读多写少。
  • BlockingQueue 是线程协作与背压组件,选型关键是是否有界、是否允许堆积。

9. 总结

  • 并发容器选型看读写比例与背压需求
  • CHM 不是强一致结构,热点 key 仍可能成为瓶颈
  • CopyOnWrite 写成本高,别用错场景
  • 阻塞队列有界优先,避免无界堆积

更多推荐