Java里为什么要引入并发集合_Java集合进化说明

普通集合如ArrayList、HashMap非线程安全,多线程读写易致ConcurrentModificationException、数据丢失或结构损坏;Vector和Hashtable虽同步但粒度粗、性能差;并发集合如ConcurrentHashMap(分段锁/CAS+volatile)、CopyOnWriteArrayList等通过细粒度锁、无锁设计和原子操作解决并发问题。

为什么普通集合在多线程下会出错

Java 的 ArrayListHashMapHashSet 等集合类默认不是线程安全的。当多个线程同时读写同一个实例时,可能触发 ConcurrentModificationException,或产生数据丢失、结构损坏(比如链表成环)、甚至无限循环(JDK 7 中 HashMap 扩容时的典型问题)。

根本原因在于:它们没对修改操作加锁,也没做可见性/原子性保障。比如 size++ 在字节码层面是“读-改-写”三步,非原子;而迭代器遍历时依赖内部 modCount 校验,一旦被其他线程悄悄修改,就直接抛异常。

VectorHashtable 为什么不够用

早期 Java 提供了 VectorHashtable,它们的方法基本都加了 synchronized,但粒度太粗——整个方法锁住整个对象。这意味着即使两个线程操作的是不同索引的元素,也会互相阻塞。

实际场景中,这导致严重性能瓶颈。比如一个高并发计数场景,100 个线程往 Vector 尾部添加元素,结果变成串行排队;而更合理的做法是只锁扩容逻辑或分段控制。

  • Vectoradd()get() 都同步,但读多写少时读操作也被拖慢
  • Hashtable 不允许 null 键/值,与现代开发习惯脱节
  • 两者都没提供批量操作的原子性支持(如“不存在才 put”这种常见需求)

并发集合是怎么解决这些问题的

从 JDK 5 开始,java.util.concurrent 包引入了真正为并发设计的集合,核心思路是:分离读写、缩小锁粒度、用 CAS 替代锁、提供原子复合操作。

ConcurrentHashMap 为例:

  • JDK 7 使用分段锁(Segment 数组),默认 16 段,不同段之间可并行写入
  • JDK 8 彻底重构:取消分段,改用 Node 数组 + 链表/红黑树,写操作只锁具体桶(synchronized 锁单个 Node),读操作完全无锁(靠 volatileUnsafe
  • 提供 computeIfAbsent()merge() 等原子方法,避免“先查后put”的竞态

类似地,CopyOnWriteArrayList 适合读远多于写的场景(如监听器列表),写操作复制整个数组,读不加锁;Concurren

tLinkedQueue 基于无锁队列(CAS+volatile),适用于高吞吐生产消费。

选错并发集合会带来什么代价

不是所有并发集合都适合所有场景。盲目替换可能引发隐性问题:

  • CopyOnWriteArrayList 存储高频更新的数据(如实时指标),会导致频繁数组复制和 GC 压力飙升
  • 在需要强一致性的地方误用 ConcurrentHashMap(它不保证迭代过程看到最新写入,也不支持全局加锁),可能读到过期状态
  • ConcurrentSkipListMap 虽然支持排序和并发,但比 ConcurrentHashMap 慢不少,除非真需要 firstKey() 或范围查询

最常被忽略的一点:ConcurrentHashMapsize() 方法在高并发下返回的是近似值(JDK 8 后),因为它不加锁统计;如果业务逻辑依赖精确大小(比如“满 100 条触发上报”),必须自己用原子计数器配合维护。