ConcurrentModificationException是因遍历时调用集合remove()破坏modCount与expectedModCount一致性所致;唯一安全方式是Iterator.remove()或removeIf(),其同步更新预期计数器。

因为在 foreach 循环中调用集合自身的 remove() 方法,会破坏迭代器的内部一致性校验机制,触发 ConcurrentModificationException。
底层依赖迭代器,且是“快速失败”设计
Java 的 foreach 本质是语法糖,编译后会转为 Iterator 迭代过程:
→ 调用 iterator.hasNext() 判断是否继续
→ 调用 iterator.next() 取出下一个元素
→ 每次调用 next() 前,都会执行 checkForComodification() 校验
该方法检查两个关键字段是否一致:
• modCount:集合结构修改次数(add/remove 都会使它 +1)
• expectedModCount:迭代器创建时记录的 modCount 初始值
一旦集合被外部(如 list.remove())修改,modCount 就会变,而 expectedModCount 不变 → 校验失败 → 抛异常。
为什么 Iterator.remove() 却安全?
因为 Iterator.remove() 是配套设计的删除方式,它在删除元素的同时会同步更新 expectedModCount,使其与当前 modCount 保持一致:
- 调用
it.remove()时,内部会先校验合法性(必须刚调用过next()) - 真正删除后,立刻把
expectedModCount设为当前modCount - 下一次
next()或hasNext()就不会触发校验失败
不是真“并发”,而是单线程下的结构不一致
这个异常名称里的 “Concurrent” 容易误导,其实它和多线程无关。即使只有主线程,只要在迭代过程中用集合自身方法改了结构,就满足“非法修改”条件。
常见误判场景:
• 吞掉异常(比如 try-catch 但没处理),导致循环看似卡住或逻辑错乱
• 删除中间元素后,后续元素前移,但游标仍按原索引推进 → 跳过下一个元素
• 在某些边界条件下,反复处理同一位置,看起来像“死循环”
其他不安全但容易忽略的方式
以下写法同样会触发异常:
for (String s : list) { if (cond) list.remove(s); }list.forEach(s -> { if (cond) list.remove(s); });- 用增强 for 遍历 Map.keySet() / values() 时,直接调用 map.remove(key)
它们都绕过了迭代器的协调机制,属于“外部修改”。

















