concurrentmodificationexception在单线程下会触发,根本原因是modcount与expectedmodcount不一致:集合结构性修改时modcount自增,而迭代器创建时拷贝的expectedmodcount未同步更新,每次next()/hasnext()前校验失败即抛异常。

ConcurrentModificationException 在单线程下会触发,根本原因不是“并发”,而是集合内部的结构一致性被破坏——具体表现为 modCount 和迭代器持有的 expectedModCount 不匹配。
modCount 与 expectedModCount 的失同步机制
ArrayList、HashMap 等非线程安全集合都维护一个 modCount(修改计数器),每次调用 add()、remove()、clear() 等结构性修改方法时,该值自增。而迭代器(如 ArrayList.Itr)在创建时会将当前 modCount 值复制为自己的 expectedModCount。此后每次调用 next() 或 hasNext() 前,都会执行 checkForComodification() 方法校验二者是否相等。
一旦不等,立刻抛出 ConcurrentModificationException——这个检查是即时的、确定性的,与线程数量无关。
典型单线程误操作场景
- 使用 for-each 循环遍历时,直接调用
list.remove(obj)或list.add(obj) - 用
Iterator遍历,但删除时写成list.remove(index)或list.remove(obj),而非iterator.remove() - 一个集合上同时存在多个活跃迭代器,其中一个调用了
remove(),另一个继续next() - 在迭代中途调用
list.clear()或list.addAll(...)
为什么 iterator.remove() 是安全的?
iterator.remove() 方法内部做了两件事:
- 真正执行元素删除(委托给集合的
remove(int index)) - 同步更新自身的
expectedModCount = modCount
这样就保证了下次调用 next() 时,校验仍能通过。这是唯一被设计为“与迭代过程兼容”的删除方式。
规避思路:绕过状态校验或改用无校验机制
- 改用
Collection.removeIf(Predicate)(Java 8+),它由集合自身控制遍历与删除逻辑,不暴露外部迭代器 - 收集待删元素,循环结束后统一调用
list.removeAll(toRemove) - 反向遍历索引(
for (int i = list.size()-1; i >= 0; i--)),避免下标偏移且不触发迭代器校验 - 读多写少场景可换用
CopyOnWriteArrayList,其迭代器基于快照,不检查modCount











