Java单线程遍历集合时调用list.remove()会抛ConcurrentModificationException,因Iterator的fail-fast机制通过modCount与expectedModCount比对检测结构性修改;仅Iterator.remove()能同步更新expectedModCount从而安全删除。

Java 中对集合进行遍历时修改(比如删除元素),直接调用 list.remove() 或 set.remove() 会触发 ConcurrentModificationException。这不是因为多线程,而是因为单线程下 Iterator 的“快速失败”(fail-fast)机制检测到了结构性修改。用 Iterator 自带的 remove() 方法,就能安全删除当前遍历到的元素。
为什么直接 remove 会报错
每个集合(如 ArrayList、HashMap)内部维护一个 modCount(修改计数器)。创建迭代器时会记录初始值(expectedModCount)。每次调用 add、remove 等结构性修改方法,modCount 就加 1;而每次调用 iterator.next() 前,都会检查两者是否一致。不一致就抛出异常。
但 Iterator.remove() 是个例外:它会在删除后同步更新 expectedModCount,所以不会触发检查失败。
正确写法:用 Iterator.remove() 删除元素
必须在调用 next() 获取元素之后、且仅一次调用 remove()。不能跳过 next() 直接删,也不能连续调两次 remove()(会抛 IllegalStateException)。
- ✅ 正确示例(删除所有偶数):
List<integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
Iterator<integer> it = list.iterator();
while (it.hasNext()) {
Integer num = it.next();
if (num % 2 == 0) {
it.remove(); // 安全删除
}
}
// list 变为 [1, 3, 5]</integer></integer>其他安全方案对比
-
for-each 循环不行:本质就是用 Iterator,但隐藏了 remove 调用入口,无法调用
it.remove(),所以删除仍会报错。 -
倒序 for 循环可行(仅限 List):从索引
size-1往前遍历,删除不影响后续未访问索引,但逻辑稍绕,且不适用于 Set/Map。 -
收集待删元素再批量删:先用新集合存要删的项,遍历完再调
list.removeAll(toRemove),适合条件复杂或需多次判断的场景,但额外占用内存。 - 使用并发集合(如 CopyOnWriteArrayList):底层复制数组,允许遍历时修改,但只适合读多写少、数据量小的场景,有性能和内存开销。
注意点和常见误区
- Iterator.remove() 只能删除刚通过 next() 返回的那个元素;不能删上一个、也不能删下一个。
- 多个线程共用同一个 Iterator 仍会出问题 —— Iterator 本身不是线程安全的,即使集合是线程安全的(如
ConcurrentHashMap的 keySet 迭代器也只保证弱一致性,不支持 remove)。 - 增强 for 循环中写
list.remove(x)看似简洁,实际等价于在 Iterator 遍历中途调外部 remove,必然抛异常。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











