concurrentmodificationexception在单线程中出现根本不是并发问题,而是fail-fast机制检测到遍历时调用集合自身add()/remove()导致modcount与expectedmodcount不一致;正确做法是使用iterator.remove()、removeif()或倒序索引删除。

在普通循环(比如 for-each 或 while + Iterator)中直接调用集合自身的 add() 或 remove() 方法,会触发 ConcurrentModificationException,根本原因不是“并发”字面意义的多线程,而是 Java 集合的 fail-fast(快速失败)机制 在检测到结构性修改不一致时主动抛出异常。
为什么普通单线程也会报错?
Java 的大多数集合类(如 ArrayList、HashMap)内部维护一个 modCount 计数器,记录集合被结构性修改(增、删、清空)的次数。当你调用 iterator() 时,迭代器会把当前的 modCount 值存为 expectedModCount。之后每次调用 next() 或 hasNext(),都会执行 checkForComodification() —— 比较两者是否相等。
一旦你在循环体里用 list.remove(x) 删除元素,modCount 就会自增,但迭代器里的 expectedModCount 还是旧值,下次 next() 就立即抛出异常。
for-each 循环本质就是 Iterator
for (String s : list) 是语法糖,编译后等价于:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
Iterator
while (it.hasNext()) {
String s = it.next(); // 这里会 checkForComodification()
// 如果此处写 list.remove(s),modCount 变了 → 下次 next() 失败
}
哪些操作算“结构性修改”?
以下行为都会使 modCount++,进而破坏迭代器预期:
- 调用
list.add()、list.remove()、list.clear() - 调用
map.put()、map.remove()(对 HashMap 等) - 即使只改元素内容(如
list.set(i, x))不算结构性修改,不会触发该异常
安全修改的正确姿势
想边遍历边删/改,必须走迭代器自己提供的方法,或绕开原集合结构:
- 用
iterator.remove():这是唯一被允许的、与遍历同步的删除方式 - 用
list.removeIf(predicate):内部已做兼容处理,推荐用于条件删除 - 用
stream().filter().collect():生成新集合,原集合不动 - 先收集待删元素,循环结束后统一删(
list.removeAll(toRemove)) - 多线程场景下换用线程安全集合,如
CopyOnWriteArrayList
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










