java集合线程安全与否的核心在于是否对共享状态读写做同步控制,非线程安全集合如arraylist、hashmap并发修改会导致数据丢失或异常,而线程安全集合通过锁、cas或复制等机制保障原子性与可见性。

Java 集合类的线程安全与否,核心差异在于**是否对共享状态的读写操作做了同步控制**,而非“能不能用”。非线程安全集合(如 ArrayList、HashMap)在多线程并发修改时,会因竞态条件导致数据丢失、数组越界、死循环甚至 ConcurrentModificationException;而线程安全集合通过锁、CAS 或不可变复制等机制,保障操作的原子性与可见性。
底层同步机制完全不同
非线程安全集合不做任何并发防护,方法体里没有 synchronized,也不调用 Unsafe 或 Lock。比如 ArrayList.add() 仅执行 elementData[size++] = e —— 这个赋值和自增不是原子操作,两个线程同时执行可能只完成一次 size 更新。
线程安全集合则主动干预执行路径:
-
Vector / Hashtable:每个 public 方法都加了
synchronized,锁的是整个对象实例,粒度粗,吞吐低; - Collections.synchronizedXxx():内部包装一个 mutex 锁对象,所有方法委托前先加锁,行为类似 Vector,但可自定义锁对象;
-
ConcurrentHashMap:JDK 8+ 使用
CAS + synchronized锁单个桶(Node),读操作完全无锁,写冲突只影响局部; - CopyOnWriteArrayList:写操作全程加锁 + 复制新数组,读操作连 volatile 读都不需要,直接返回原始引用。
实际表现差异一目了然
用 100 个线程各向集合添加 1000 个元素,结果对比鲜明:
-
ArrayList:最终 size 常为 99xxx(远小于 100,000),出现重复覆盖或扩容失败; -
Vector或Collections.synchronizedList(new ArrayList()):size 稳定为 100,000,但耗时可能是CopyOnWriteArrayList的 3–5 倍; -
CopyOnWriteArrayList:size 正确,但写耗时极高(每次 add 都复制万级数组),适合监听器列表这类写极少、读极多场景; -
ConcurrentHashMap:put 操作平均延迟低、吞吐高,且支持computeIfAbsent、merge等原子复合操作,无需额外同步。
不能只看“是否报错”,要看语义是否正确
有些非线程安全集合在并发读时看似正常(比如多个线程只调 get()),但一旦有线程同时修改结构(如 resize() 或 modCount 变更),其他线程的迭代器就可能抛出 ConcurrentModificationException,或陷入死循环(HashMap 在 JDK 7 的环形链表问题)。这说明“没报错 ≠ 安全”,真正安全是指:无论读写比例如何、线程如何交错执行,结果都符合预期语义。
例如用 HashMap 缓存用户登录态,两个线程同时触发 put,可能一个覆盖另一个,导致用户被莫名登出 —— 这不是异常,却是业务级错误。
选型关键看读写特征和一致性要求
不要默认用 synchronized 包装,也不要迷信“线程安全=万能”。合理选择取决于真实负载:
- 读占比 > 95%,写极少(如配置监听器、事件订阅者列表)→
CopyOnWriteArrayList; - 读写均衡,高并发,需强一致性(如订单缓存、库存计数)→
ConcurrentHashMap; - 仅需简单同步,且线程数少、QPS 低(如工具类内部临时集合)→
Collections.synchronizedMap()可接受; - 遗留系统兼容或必须用传统 API →
Vector/Hashtable,但应明确标注“不推荐新代码使用”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











