ConcurrentModificationException 在单线程中遍历时调用集合的结构修改方法(如 remove/add/clear)即触发,因迭代器校验 modCount 与 expectedModCount 不一致;安全删除须用迭代器自身 remove()、removeIf() 或 removeAll()。

ConcurrentModificationException 是怎么触发的
这个异常不是因为多线程并发修改才抛出的——单线程遍历集合时边 iterator.next() 边调用 list.remove() 就会立即触发。根本原因是 Java 集合(如 ArrayList、HashMap)内部维护了一个 modCount 计数器,每次结构修改(增/删/清空)就加 1;而迭代器在创建时会把当时的 modCount 快照存为 expectedModCount,后续每次 next() 或 hasNext() 都会校验两者是否一致,不一致就直接抛 ConcurrentModificationException。
哪些操作会引发校验失败
以下行为在使用 Iterator 或增强 for 循环(本质也是迭代器)时,都会导致 modCount 和 expectedModCount 不匹配:
-
list.remove(obj)或list.add(obj)—— 直接调用集合方法 -
map.put(key, value)或map.remove(key)—— 对HashMap等做结构变更 -
collection.clear()—— 清空整个集合 - 即使只是在另一个线程里调用了这些方法,也会立刻让正在遍历的迭代器失效
安全删除的三种写法
必须通过迭代器自身的 remove() 方法来删元素,它会在删除后同步更新 expectedModCount:
Iteratorit = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.startsWith("a")) { it.remove(); // ✅ 正确:由迭代器控制 } }
其他可行方式:
立即学习“Java免费学习笔记(深入)”;
- 收集待删元素,遍历完再调用
list.removeAll(toRemove) - 用
removeIf()(JDK 8+):list.removeIf(s -> s.startsWith("a")) - 改用线程安全集合(如
CopyOnWriteArrayList),但注意它只适合读多写少场景,且迭代器不反映实时修改
增强 for 循环和 fail-fast 的关系
写 for (String s : list) 看似简洁,但它底层仍会调用 list.iterator(),所以一样受 ConcurrentModificationException 约束。很多人误以为“没显式写 iterator 就不会出错”,其实只要在循环体里调了 list.add() 或 list.remove(),运行时必崩。
真正容易被忽略的是:这个检查只针对「结构性修改」,修改元素内容(比如 list.get(0).setName("x"))不会触发异常——但如果你依赖的是对象状态变化后的逻辑判断,那可能产生隐蔽的语义错误,而不是抛异常。










