在Java里集合修改异常如何产生_JavaConcurrentModificationException说明

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

ConcurrentModificationException 是怎么触发的

这个异常不是因为多线程并发修改才抛出的——单线程遍历集合时边 iterator.next() 边调用 list.remove() 就会立即触发。根本原因是 Java 集合(如 ArrayListHashMap)内部维护了一个 modCount 计数器,每次结构修改(增/删/清空)就加 1;而迭代器在创建时会把当时的 modCount 快照存为 expectedModCount,后续每次 next()hasNext() 都会校验两者是否一致,不一致就直接抛 ConcurrentModificationException

哪些操作会引发校验失败

以下行为在使用 Iterator 或增强 for 循环(本质也是迭代器)时,都会导致 modCountexpectedModCount 不匹配:

  • list.remove(obj)list.add(obj) —— 直接调用集合方法
  • map.put(key, value)map.remove(key) —— 对 HashMap 等做结构变更
  • collection.clear() —— 清空整个集合
  • 即使只是在另一个线程里调用了这些方法,也会立刻让正在遍历的迭代器失效

安全删除的三种写法

必须通过迭代器自身的 remove() 方法来删元素,它会在删除后同步更新 expectedModCount

Iterator it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.startsWith("a")) {
        it.remove(); // ✅ 正确:由迭代器控制
    }
}

其他可行方式:

  • 收集待删元素,遍历完再调用 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"))不会触发异常——但如果你依赖的是对象状态变化后的逻辑判断,那可能产生隐蔽的语义错误,而不是抛异常。