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

为什么普通集合在多线程下会出错
Java 的 ArrayList、HashMap、HashSet 等集合类默认不是线程安全的。当多个线程同时读写同一个实例时,可能触发 ConcurrentModificationException,或产生数据丢失、结构损坏(比如链表成环)、甚至无限循环(JDK 7 中 HashMap 扩容时的典型问题)。
根本原因在于:它们没对修改操作加锁,也没做可见性/原子性保障。比如 size++ 在字节码层面是“读-改-写”三步,非原子;而迭代器遍历时依赖内部 modCount 校验,一旦被其他线程悄悄修改,就直接抛异常。
Vector 和 Hashtable 为什么不够用
早期 Java 提供了 Vector 和 Hashtable,它们的方法基本都加了 synchronized,但粒度太粗——整个方法锁住整个对象。这意味着即使两个线程操作的是不同索引的元素,也会互相阻塞。
实际场景中,这导致严重性能瓶颈。比如一个高并发计数场景,100 个线程往 Vector 尾部添加元素,结果变成串行排队;而更合理的做法是只锁扩容逻辑或分段控制。
立即学习“Java免费学习笔记(深入)”;
-
Vector的add()和get()都同步,但读多写少时读操作也被拖慢 -
Hashtable不允许null键/值,与现代开发习惯脱节 - 两者都没提供批量操作的原子性支持(如“不存在才 put”这种常见需求)
并发集合是怎么解决这些问题的
从 JDK 5 开始,java.util.concurrent 包引入了真正为并发设计的集合,核心思路是:分离读写、缩小锁粒度、用 CAS 替代锁、提供原子复合操作。
以 ConcurrentHashMap 为例:
- JDK 7 使用分段锁(
Segment数组),默认 16 段,不同段之间可并行写入 - JDK 8 彻底重构:取消分段,改用
Node数组 + 链表/红黑树,写操作只锁具体桶(synchronized锁单个Node),读操作完全无锁(靠volatile和Unsafe) - 提供
computeIfAbsent()、merge()等原子方法,避免“先查后put”的竞态
类似地,CopyOnWriteArrayList 适合读远多于写的场景(如监听器列表),写操作复制整个数组,读不加锁;ConcurrentLinkedQueue 基于无锁队列(CAS+volatile),适用于高吞吐生产消费。
选错并发集合会带来什么代价
不是所有并发集合都适合所有场景。盲目替换可能引发隐性问题:
- 用
CopyOnWriteArrayList存储高频更新的数据(如实时指标),会导致频繁数组复制和 GC 压力飙升 - 在需要强一致性的地方误用
ConcurrentHashMap(它不保证迭代过程看到最新写入,也不支持全局加锁),可能读到过期状态 -
ConcurrentSkipListMap虽然支持排序和并发,但比ConcurrentHashMap慢不少,除非真需要firstKey()或范围查询
最常被忽略的一点:ConcurrentHashMap 的 size() 方法在高并发下返回的是近似值(JDK 8 后),因为它不加锁统计;如果业务逻辑依赖精确大小(比如“满 100 条触发上报”),必须自己用原子计数器配合维护。










