因为在Java集合遍历时直接调用remove()会触发fail-fast机制:modCount与expectedModCount不一致,导致ConcurrentModificationException;正确做法是使用Iterator.remove()同步更新计数器。

为什么在Java集合遍历时直接调用remove()会抛ConcurrentModificationException
因为大多数Java集合(如ArrayList、HashMap、LinkedList)内部实现了快速失败(fail-fast)机制。它不允许多线程或单线程中「遍历 + 结构性修改」并存——哪怕只是同一个线程里先iterator.next()再list.remove(obj),也会触发检查失败。
核心原因是:集合维护一个modCount(修改计数器),每次add()、remove()等结构性操作都会递增它;而迭代器在创建时会记录当时的expectedModCount。只要两者不一致,下一次调用next()或hasNext()就会立即抛出ConcurrentModificationException。
正确删除方式:必须用Iterator.remove()
这是唯一被设计为与遍历兼容的删除方法。它会在删除元素的同时同步更新expectedModCount,避免校验失败。
- 不能用
for-each循环(本质是隐式Iterator),因为它不暴露remove()方法 - 不能在
while (it.hasNext())中调用list.remove(),必须调用it.remove() -
Iterator.remove()只能紧跟在next()之后调用一次,否则抛IllegalStateException
Listlist = new ArrayList<>(Arrays.asList("a", "b", "c", "b")); Iterator it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("b".equals(s)) { it.remove(); // ✅ 正确:调用迭代器自己的remove } } // list 现在是 ["a", "c"]
其他可行方案及适用场景
如果逻辑复杂或需多次删除,可考虑替代策略,但要注意语义和性能差异:
立即学习“Java免费学习笔记(深入)”;
-
removeIf(Predicate)(JDK 8+):简洁安全,底层仍用Iterator,推荐用于简单条件删除 - 倒序
for循环(for (int i = list.size()-1; i >= 0; i--)):适用于ArrayList等支持随机访问的集合,避免索引错位,但不适用于LinkedList(性能差) - 收集待删元素再批量删:
list.removeAll(toRemove):适合多条件判断后集中处理,但需额外空间存临时集合 - 使用
CopyOnWriteArrayList:线程安全、允许遍历时修改,但仅适合读多写少场景,且每次写操作都复制整个数组,开销大
容易忽略的关键点
快速失败不是线程安全保证,而是一种调试辅助机制——它只在「可能出错」时尽早报错,但不保证「所有并发修改都能捕获」。比如,某些修改发生在迭代器检查间隙,就可能漏掉异常(虽然概率低)。另外,HashMap的entrySet().iterator()同样受此约束,map.remove(key)在遍历时也会失败,必须用it.remove()或removeIf。










