arraylist和hashmap抛concurrentmodificationexception是fail-fast机制的主动防御,非bug;它们通过modcount与expectedmodcount校验并发修改,暴露非线程安全的误用。

为什么 ArrayList 和 HashMap 在并发修改时抛 ConcurrentModificationException
这不是“bug”,而是 fail-fast 机制的主动防御行为:它们内部维护一个 modCount 计数器,每次结构修改(add/remove)就自增;而迭代器在创建时会记录当时的 expectedModCount,后续每次 next() 都校验两者是否一致。一旦不等,立刻抛异常——它不保证线程安全,只快速暴露错误。
常见误用场景包括:在 for-each 循环中调用 list.remove(),或多个线程同时读写同一个 HashMap。此时异常不是偶然,是必然,说明你正在用非线程安全容器做并发操作。
哪些容器能真正替代 ArrayList 和 HashMap 做高并发读写
别试图给 ArrayList 加 synchronized 块——锁粒度太粗,吞吐量崩得比不用还快。要选设计上支持并发的实现:
-
CopyOnWriteArrayList:适合读多写少(如监听器列表),写操作复制整个数组,读完全无锁;但写代价高、不支持null、迭代器看到的是快照,无法反映实时变更 -
ConcurrentHashMap:默认分段锁(JDK 8+ 改为 CAS + synchronized 锁单个桶),支持高并发读写;注意size()不是强一致的,containsKey()和get()无锁,但putIfAbsent()是原子的 - 如果需要有序且并发安全,
ConcurrentSkipListMap比TreeMap+ 外层锁更合理,基于跳表,支持近似 O(log n) 并发操作
ConcurrentHashMap 的常见误用和边界条件
它不是万能的“线程安全 HashMap”——很多逻辑错误不会抛异常,但结果错得隐蔽:
- 复合操作非原子:
map.get(key) == null && map.put(key, value)依然有竞态,必须用putIfAbsent(key, value)或computeIfAbsent(key, f) - 遍历时若需强一致性(比如统计所有值之和),不能直接用 for-each,应改用
mappingCount()+forEach()或收集到本地再处理,因为迭代过程允许写入 - JDK 8+ 中
keySet().iterator()返回弱一致性迭代器,可能跳过刚插入的项,也可能重复返回已删除项——这属于设计预期,不是 bug
性能敏感场景下,比容器选择更关键的其实是控制并发模型
用对容器只是基础。真正影响吞吐的,往往是数据访问模式本身:
- 避免让多个线程争抢同一把锁(比如所有操作都打到
ConcurrentHashMap同一个 key 对应的桶),可考虑哈希扰动或分片键设计 - 如果业务允许最终一致性,优先用无锁结构(如
AtomicIntegerArray、LongAdder)代替容器封装 - 写操作频繁且顺序无关时,用队列缓冲(
LinkedBlockingQueue或Disruptor)批量落库,比直接并发写容器更可控
fail-fast 容器不是敌人,是提醒你“这里不该并发”的红灯。绕开它不靠技巧,靠厘清数据归属权和访问契约——这点比记住哪个类名带 Concurrent 前缀重要得多。










