发布于2026-05-21 阅读(0)
扫一扫,手机访问
ConcurrentModificationException 的根本原因是遍历中发生结构性修改,而非迭代器数量;每个迭代器持有独立 expectedModCount,仅当 modCount 不匹配时触发 fail-fast 机制。

在Ja va开发中,ConcurrentModificationException 这个异常恐怕不少人都遇到过。很多人第一反应是:“是不是因为我开了多个迭代器(Iterator)同时操作同一个集合?” 其实,这个直觉只对了一半。真正引发问题的,往往不是迭代器的数量,而是那个在遍历过程中“偷偷”修改了集合结构的动作——无论这个动作来自另一个迭代器、集合自身的方法,还是同一线程里的其他代码。
要理解这一点,得先看看迭代器的工作原理。每个迭代器在创建的那一刻,都会悄悄记下集合当前的“修改版本号”——也就是 modCount 的值,并将其存为自己的 expectedModCount。只要后续没有任何代码去改动集合的结构(比如增删元素),那么无论创建多少个迭代器,它们各自安好,完成遍历,都不会出问题。
举个例子:线程A创建了iterator1并顺利遍历完整个集合,线程B创建了iterator2也完成了遍历,这个过程是安全的。但如果在线程A的iterator1遍历到一半时,线程B突然调用了 list.add() 插入了一个新元素,那么集合的 modCount 就变了。等到iterator1再次调用 next() 方法时,它一对比发现手里的 expectedModCount 和集合当前的 modCount 对不上,便会立刻抛出 ConcurrentModificationException。
说到底,这个异常是Ja va集合“快速失败(fail-fast)”机制在起作用。它不关心到底是谁修改了集合,它只检查一个事实:“当前集合实际的修改次数,是否还和我开始遍历时记录的那个版本号一致?” 一旦不一致,就立刻报错,目的是尽早暴露潜在的并发问题,避免数据状态进入不可预测的混乱。
哪些是典型的高危场景呢?
for-each 循环(其底层也是迭代器)遍历一个 ArrayList,却在循环体内直接调用 list.remove(x) 来删除元素。Iterator 遍历集合,另一个线程却调用了 list.add() 来插入新元素。ListIterator 同时操作同一个 ArrayList,其中一个调用了 add() 或 remove()。这里有个关键概念:只有**结构性修改(structural modification)** 才会改变那个关键的 modCount 值。哪些操作算结构性修改呢?
add()、remove()、clear(),以及 retainAll()、removeAll() 这类批量操作。set() 方法,它只是替换某个位置的元素,不改变集合大小,因此不算结构性修改,不会触发异常。get()、contains()、size(),那就更加安全了。如果业务逻辑确实要求在遍历过程中增删元素,那该怎么办?硬来肯定不行,得遵循“规矩”。
iterator.remove() 方法。注意,它只能删除刚刚通过 next() 返回的那个元素,调用前必须先调用 next()。ListIterator):使用 listIterator.add() 方法。这个方法很“聪明”,它在添加元素的同时,会同步更新迭代器内部的 expectedModCount,从而避免后续操作抛出异常。Collection.removeIf(Predicate) 方法。这是一个原子操作,其内部实现已经巧妙地规避了并发修改检查,既安全又简洁。CopyOnWriteArrayList,而需要高性能并发访问的键值对存储则首选 ConcurrentHashMap。理解这些规则,本质上是在理解Ja va集合框架为平衡便利性与安全性所做的设计。下次再遇到这个异常时,不妨先冷静下来,检查一下到底是哪段代码在遍历过程中“越了界”。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8