concurrentmodificationexception根本原因是fail-fast校验失败:hashmap等map实现通过modcount标记结构变更,迭代器用expectedmodcount记录初始值,next()前比对二者,不等即抛异常;单线程遍历中直接调用map.remove()或put()也会触发,因modcount改变而expectedmodcount未同步;仅iterator.remove()能安全删除,因其内部同步更新expectedmodcount;concurrenthashmap例外,采用弱一致性设计,不依赖modcount机制。

ConcurrentModificationException 在 Map 实现类(如 HashMap、LinkedHashMap、TreeMap)中触发,根本原因不是“多线程”本身,而是迭代器检测到结构修改与预期不一致——它是一次 fail-fast 校验失败,不是并发安全机制。
modCount 是结构变更的“指纹”,不是计数器
每个 Map 实现内部都维护一个 modCount 字段(如 HashMap 中定义在父类 AbstractMap),每次调用 put、remove、clear 等改变集合结构的方法时,modCount 就自增一次。这个值不记录操作次数,只标记“结构是否变了”。
当你调用 entrySet().iterator() 开始遍历,迭代器会立刻把当前 modCount 复制为自己的 expectedModCount。后续每次调用 next() 前,都会执行 checkForComodification(),比对二者是否相等。一旦不等,立刻抛 ConcurrentModificationException。
单线程也会触发,关键在“遍历中改结构”
哪怕只有一个线程,只要在 for-each 或手动 Iterator 遍历过程中,直接调用 map.remove(key) 或 map.put(k, v),就会导致 modCount 变动而 expectedModCount 滞后,校验失败。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 错误写法:for (Map.Entry
e : map.entrySet()) { if (e.getValue() == null) map.remove(e.getKey()); } - 正确写法:Iterator
> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = it.next(); if (e.getValue() == null) it.remove(); }
Iterator.remove() 为什么能绕过异常?
因为它是唯一被设计为“与迭代过程协同”的删除方式:
- it.remove() 内部会同步更新 expectedModCount,使其与当前 modCount 保持一致
- 它只能在 next() 调用之后、且未被调用过 remove() 的情况下执行,否则抛 IllegalStateException
- 它不依赖外部 map 的 remove 方法,而是由迭代器自己控制删除逻辑和状态同步
不是所有 Map 都一样:ConcurrentHashMap 是例外
ConcurrentHashMap 不基于 modCount + fail-fast,而是采用分段锁(JDK 7)或 CAS + synchronized(JDK 8+)实现弱一致性遍历。它的迭代器允许遍历时结构被其他线程修改,不会抛 ConcurrentModificationException,但可能看不到最新写入或看到重复/遗漏元素——这是设计取舍,不是 bug。
普通 HashMap、LinkedHashMap、TreeMap 则严格遵循 fail-fast 原则,把“数据不一致风险”拦在运行时早期,强制开发者显式处理读写冲突。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










