遍历中调用list.remove()会抛concurrentmodificationexception,因iterator与集合通过modcount和expectedmodcount校验一致性,不一致即fail-fast;单线程亦触发。

为什么遍历中调用 list.remove() 会抛 ConcurrentModificationException
因为 Iterator 和集合本体之间有个“暗号”:modCount。每次你调用 list.add() 或 list.remove(),集合就悄悄把 modCount 加 1;而迭代器在创建时记下当时的值,叫 expectedModCount。之后每次调用 hasNext() 或 next(),都会比对这两个数——不一致?立刻中断,抛出 ConcurrentModificationException。
这不是线程专属问题,单线程里也照崩。常见翻车现场:
- 边
iterator.next()边写list.remove(x) - 在一个
for-each循环里调用集合自身的remove() - 遍历过程中,另一个方法偷偷改了同一个
ArrayList
怎么安全删元素?只用 iterator.remove(),别碰集合的 remove()
iterator.remove() 是唯一被允许的删除方式,它会同步更新 expectedModCount,让检查通过。其他任何删除动作都绕过这个协调机制。
正确写法示例:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("tmp")) {
it.remove(); // ✅ 安全
}
}
错误写法(哪怕逻辑一模一样):
-
list.remove(s)→ 立刻触发 fail-fast -
list.remove(0)→ 同样崩,和删哪个位置无关 - 在
for-each里写list.remove(...)→ 本质还是用了集合自己的remove
ListIterator 能双向遍历,但不能解决并发修改问题
ListIterator 提供 previous()、hasPrevious(),适合需要来回扫的场景(比如解析嵌套结构、回退校验),但它和普通 Iterator 共享同一套 fail-fast 逻辑——modCount 检查照常运行。
所以别指望用 list.listIterator() 绕过异常。它只是多了一组指针操作,不是“免检通道”。如果你真需要边遍历边改结构,考虑:
- 先收集待删元素,遍历完再批量
removeAll() - 用
CopyOnWriteArrayList(仅适用于读多写少、允许弱一致性场景) - 加显式锁(如
synchronized(list)),但要小心死锁和性能损耗
for-each 底层就是 Iterator,别被语法糖骗了
for (String s : list) 看着简洁,但编译后完全等价于手动调 iterator().hasNext() 和 next()。所以它同样受 fail-fast 约束,且更隐蔽——因为你根本看不到迭代器变量,也就更容易误用 list.remove()。
如果必须在 for-each 中删元素,唯一办法是提前跳出循环,转用显式 Iterator。没有“安全的 for-each 删除语法”这回事。
容易忽略的一点:Enumeration(如 Vector.elements())不带 fail-fast,但它是历史遗留接口,不支持 remove(),也不被现代集合广泛实现,别为了躲异常倒退到 JDK 1.0。










