java集合迭代失效的核心在于modcount与expectedmodcount的“时间差”:迭代器构造时复制modcount为expectedmodcount,后续修改导致二者不等即抛concurrentmodificationexception。

Java 中集合迭代失效机制的核心隐患,就藏在 modCount 与 expectedModCount 的“时间差”里——这不是 bug,而是设计者刻意埋下的预警开关。
迭代器创建那一刻就定下了“快照契约”
以 ArrayList 为例,每次调用 iterator(),内部类 Itr 就会把当前集合的 modCount 值复制给自己的 expectedModCount:
-
modCount是 ArrayList 自己的“修改计数器”,每次add、remove、clear都自增 -
expectedModCount是迭代器私有的“预期值”,仅在构造时赋值一次,之后再不更新 - 每次
next()或hasNext()前,都会执行checkForComodification(),比对二者是否相等
多线程下“一人改、多人崩”的真实场景
假设有两个线程共享一个 ArrayList 和同一个 Iterator 实例(常见于误传、单例缓存或静态变量):
- 线程 A 调用
it.next(),进入方法前检查:modCount == expectedModCount → 通过 - 线程 B 此刻调用
list.add("x")→ modCount 从 5 变成 6 - 线程 A 继续执行
next()内部逻辑,再次检查 → 6 ≠ 5 → 立即抛ConcurrentModificationException
注意:哪怕线程 B 只是读操作(如 get()),只要它触发了结构性修改(比如扩容导致数组复制),modCount 仍会变——异常照样发生。
看似线程安全的集合也救不了迭代器
像 Collections.synchronizedList() 或 Vector,只保证单个方法(如 add())是原子的,但无法约束跨方法的迭代过程:
- 迭代需多次调用
hasNext()+next(),中间穿插其他线程的修改,检查必然失败 -
Vector迭代器虽加了synchronized,但仍是 fail-fast,不是 fail-safe;它不解决“预期值过期”问题,只是让异常出现得更可预测
源码级避坑关键点
真正规避隐患,不能靠“锁住整个循环”,而要切断 modCount 和 expectedModCount 的耦合路径:
- 每个线程独立调用
list.iterator(),确保 expectedModCount 来自各自创建时刻的 modCount - 改用
CopyOnWriteArrayList:它的迭代器构造时直接拷贝当前数组副本,后续所有写操作都在新数组上进行,旧副本完全不受影响 - 用
parallelStream()替代手写迭代:底层分片由 ForkJoinPool 管理,不依赖传统游标,天然绕开 modCount 校验
不复杂但容易忽略:问题不在“能不能并发”,而在“谁在持有哪个时刻的状态”。看清这个契约,就能避开 90% 的迭代崩溃。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











