迭代器的并发破坏:分析多个迭代器同时修改同一变量集合导致的异常
ConcurrentModificationException异常的直接原因是集合在迭代过程中发生了结构性修改,导致集合的modCount与迭代器记录的expectedModCount不一致,从而触发fail-fast机制。问题的核心并非迭代器数量,而是遍历时对集合进行了add或remove等改变结构的操作。要安全地在遍历中修改集合,应使用迭代器自身的rem
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,从而避免后续操作抛出异常。 - 批量删除:在Ja va 8及以上版本,推荐使用
Collection.removeIf(Predicate)方法。这是一个原子操作,其内部实现已经巧妙地规避了并发修改检查,既安全又简洁。 - 多线程场景:如果集合真的需要在多线程环境下被频繁读写,那么换用线程安全的集合类才是根本解决方案。例如,读多写少的场景可以考虑
CopyOnWriteArrayList,而需要高性能并发访问的键值对存储则首选ConcurrentHashMap。
理解这些规则,本质上是在理解Ja va集合框架为平衡便利性与安全性所做的设计。下次再遇到这个异常时,不妨先冷静下来,检查一下到底是哪段代码在遍历过程中“越了界”。
Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。
Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。
Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。
PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。
Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等
Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。













