concurrentmodificationexception 根源是遍历中发生结构性修改导致 modcount 与 expectedmodcount 不一致;安全方案包括 iterator.remove()、removeif()、copyonwritearraylist、concurrenthashmap 或先收集后批量删除。

因为在多线程清洗数据时直接修改共享集合,会触发 ConcurrentModificationException(并发修改异常),根本原因不是“多个线程”本身,而是遍历与结构性修改在时间上发生了冲突,且集合内部的保护机制(fail-fast)主动中断了执行。
modCount 和 expectedModCount 的信任断裂
所有 fail-fast 集合(如 ArrayList、HashMap)都维护一个 modCount 计数器,记录结构性修改(add、remove、clear 等)次数。迭代器创建时会把当时的 modCount 值复制为自己的 expectedModCount。每次调用 next() 前,都会校验两者是否一致:
- 若不一致 → 立即抛出 ConcurrentModificationException
- 迭代器自身的 remove() 会同步更新 expectedModCount,保持匹配
- 但集合自身的 remove() 或 add() 只改 modCount,完全不碰迭代器的 expectedModCount
多线程清洗中的典型危险组合
清洗逻辑常含“遍历 + 过滤删除”,一旦多个线程共用同一集合,极易踩中以下陷阱:
- 线程 A 正用 for-each 遍历 list,线程 B 同时调用 list.remove() → A 的迭代器下次 next() 就失败
- 两个线程各自创建了独立迭代器,但其中一个在线程 A 遍历中途调用了 list.add() → A 的 expectedModCount 已过期
- 即使给集合操作加 synchronized,只要锁没覆盖“遍历全过程”,校验仍会失败(因为锁只保修改原子性,不保遍历一致性)
真正安全的清洗方式
避免异常的关键是切断“遍历中发生结构性修改”这一链路,而不是单纯加锁:
- 单线程清洗:用 iterator.remove() 或 Java 8+ 的 list.removeIf(predicate)(内部已规避检查)
- 读多写少场景(如配置项、监控白名单):换用 CopyOnWriteArrayList,遍历时基于不可变快照,写操作走复制,天然免疫该异常
- 读写较均衡场景:改用 ConcurrentHashMap(注意它不支持 Iterator.remove,但支持 computeIfAbsent/compute 等线程安全更新)
- 必须强一致性且逻辑复杂:先收集待删元素,遍历结束后统一 removeAll(),或使用显式锁 + 双重检查











