ConcurrentModificationException 的本质是迭代器的快速失败校验机制,通过 modCount 与 expectedModCount 比对检测集合结构被意外修改,单线程下违反“迭代器独占修改权”也会触发,它仅作调试提示而非并发安全保障。

ConcurrentModificationException 的出现,不是因为“多线程”本身,而是因为迭代器发现集合的结构状态和自己预期的不一致。它背后是 Java 集合框架中一套轻量、即时的校验逻辑,目的不是保证并发安全,而是尽早暴露逻辑错误。
modCount 与 expectedModCount 的配对校验
每个非线程安全集合(如 ArrayList、HashMap)内部都维护一个 modCount 变量,记录结构性修改(add、remove、clear 等)的次数。当创建迭代器时,它会把当前的 modCount 值复制给自己的 expectedModCount 字段。
每次调用 next() 或 hasNext() 前,迭代器都会执行一次校验:
- 如果
modCount == expectedModCount,继续遍历 - 如果不等,立即抛出
ConcurrentModificationException
这个检查不加锁、不阻塞、不重试,纯粹是一次快照比对——所以叫“快速失败”。
为什么单线程也会触发?
很多人误以为这是“多线程专属异常”,其实只要违反了“迭代器独占结构修改权”的约定,单线程下一样会炸:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
-
for (String s : list) { list.remove(s); }—— for-each 底层用迭代器,但删除走的是集合方法,modCount 被改,expectedModCount 没同步 - 在迭代中途调用
list.add(x)或list.clear() - 多个迭代器共享一个集合,一个删、一个还在 next()
关键点在于:是否通过迭代器自身的 remove() 方法操作。只有它会在删除后主动更新 expectedModCount = modCount,维持一致性。
检测时机有盲区,不能依赖它来“保安全”
校验只发生在 next() 和 hasNext() 入口,这意味着:
- 如果修改发生在最后一次
next()之后(比如刚取完最后一个元素就 remove),后续不再调用迭代方法,异常就不会抛出 - 循环中提前
break或return,也可能跳过下一次校验 - 多线程下,A 线程刚通过
hasNext()返回 true,B 线程立刻修改集合,A 在next()时才校验——此时可能返回脏数据、null,甚至不抛异常
也就是说,没抛异常 ≠ 没问题;抛了异常 ≠ 问题被解决。它只是调试线索,不是运行保障。
常见应对方式及其适用场景
真正解决问题,得从使用习惯和集合选型入手:
- 单线程遍历中要删除:用
Iterator.remove(),或改用list.removeIf(predicate) - 需要边查边删多个元素:先收集待删项到临时集合,再调用
list.removeAll(temp) - 必须支持并发读写:换用
CopyOnWriteArrayList(适合读多写少)或ConcurrentHashMap - 对一致性要求极高(如金融、状态迁移):不用这些自动机制,改用显式锁(
ReentrantLock)配合手动同步
Fail-Fast 不是并发控制协议,它不提供原子性、隔离性或回滚能力。它只是一个善意的提醒:你写的这段遍历逻辑,很可能藏着隐蔽的竞态或误操作。

















