ConcurrentModificationException的根本原因是单线程或并发场景下,迭代器遍历时集合被非迭代器方式结构性修改,导致modCount与expectedModCount不一致而触发校验失败。

Java集合遍历时报ConcurrentModificationException,根本原因是:在使用迭代器(如for-each、iterator.next())遍历集合的过程中,集合结构被**非迭代器方式**修改了(比如直接调用list.remove()、map.put()等),而迭代器检测到这种“意外变更”,主动抛出异常来防止数据不一致。
为什么迭代器要检测并发修改?
Java多数集合类(如ArrayList、HashMap、LinkedList)内部维护一个modCount(修改计数器),每次结构性修改(增、删、清空)都会让modCount自增。迭代器创建时会把当前modCount值存为expectedModCount;每次调用next()或hasNext()前,都会检查二者是否一致。不一致就说明集合被“偷偷改了”,立刻抛出ConcurrentModificationException。
这不是线程安全问题专属——即使单线程下,边遍历边用集合自身方法修改,也会触发该异常。
常见错误写法与正确替代方案
以下操作在遍历时极易出错:
立即学习“Java免费学习笔记(深入)”;
-
错误:用
for-each循环中直接调用list.remove(obj) -
错误:用
Iterator遍历时,调用list.add()或map.put() - 错误:嵌套遍历两个集合,外层修改了内层正在遍历的集合
✅ 正确做法:
- 需要删除元素 → 使用迭代器自身的
remove()方法(它会同步更新expectedModCount) - 需要添加/批量修改 → 先收集待操作元素,遍历结束后再统一处理(如用临时
List存要删的对象,最后调用list.removeAll(temp)) - 多线程场景 → 改用线程安全集合(如
CopyOnWriteArrayList、ConcurrentHashMap),它们对迭代器做了特殊设计,允许遍历时安全修改
哪些集合不会抛这个异常?
不是所有集合都严格校验modCount:
-
CopyOnWriteArrayList:迭代器基于快照,修改原集合不影响当前迭代,不抛异常 -
ConcurrentHashMap:迭代器弱一致性,允许并发修改,不抛异常(但可能看不到最新修改) -
java.util.concurrent包下的多数集合都绕过了modCount机制,以牺牲强一致性换取并发性能
⚠️ 注意:Vector和Stack虽是线程安全的,但它们的迭代器仍检查modCount,单线程遍历时误删仍会报错。
调试与定位技巧
遇到该异常时,不要只看堆栈最上面那行。关键线索在异常消息里:java.util.ConcurrentModificationException: null本身不带上下文,但可通过以下方式快速定位:
- 启用IDE断点:在迭代器
checkForComodification()方法处打断点(如ArrayList$Itr.checkForComodification) - 检查调用栈中
remove()、add()等修改方法是否出现在for-each或iterator.next()附近 - 临时加日志:在疑似修改位置打印线程名+堆栈(
Thread.currentThread().getStackTrace()),确认是否同一迭代周期内发生
不复杂但容易忽略。










