java集合fail-fast本质是逻辑一致性校验,非线程同步协议;modcount未用volatile修饰,多线程下可见性不保证,仅“尽力而为”检测cme,官方明确其仅用于调试而非正确性保障。

Java 集合的“迭代失效”(即 Fail-Fast)机制,本质是逻辑一致性校验,不是线程同步协议;它对 modCount 的读写是否具备内存可见性,取决于具体集合实现——但**这个可见性问题不影响 Fail-Fast 的核心行为,也不决定 CME 是否抛出**。
modCount 本身不是为多线程同步设计的
以 ArrayList 为例,其 modCount 声明为:
它没有被 volatile 修饰。这意味着:在多线程场景下,一个线程对 modCount 的递增(如调用 add()),**不一定立即对其他线程可见**。
但这恰恰是设计使然——Fail-Fast 不承诺强一致性,只做“尽力而为”的检测。官方文档明确指出:
"Fail-fast behavior cannot be guaranteed... it would be wrong to write a program that depended on this exception for its correctness."也就是说,CME 是调试辅助工具,不是同步控制手段。
JVM 内存模型下,可见性缺失反而让 CME 更“宽松”
假设线程 A 修改了集合(modCount++),但因缺少 volatile 或同步块,线程 B 的迭代器仍看到旧的 modCount 值:
- 线程 B 的
expectedModCount和当前读到的modCount暂时一致 → 不抛异常 - 这会导致“本该失败却没失败”,即 漏报(false negative)
- 但不会出现“不该失败却失败”(false positive),因为
expectedModCount只会在创建迭代器时读一次,之后只靠本地变量比对
所以,内存可见性弱,只会让 Fail-Fast 机制更不敏感,而不是更严格。
真正影响 CME 触发的关键,是操作顺序与执行时机
CME 抛出与否,取决于「结构性修改发生」和「迭代器下一次 checkForComodification() 执行」之间是否有 happens-before 关系。常见情况:
-
单线程中必触发:修改发生在
next()调用前,下次next()必校验,且读的是本线程最新值(无可见性问题) -
多线程中可能不触发:线程 A 修改后未同步,线程 B 迭代器仍用旧
modCount,校验通过;直到某次重读才暴露(或永远不暴露) -
加锁能强制可见性:若所有修改和遍历都用同一把锁(如
Collections.synchronizedList),锁的释放-获取建立 happens-before,modCount变更对迭代线程可见 → 更大概率触发 CME,但这仍是副作用,不是设计目标
想真正解决并发修改?别依赖 modCount 校验
如果你的应用需要多线程安全地遍历+修改集合,应该绕开 Fail-Fast 的检测逻辑,选择真正支持并发的方案:
-
CopyOnWriteArrayList:写时复制,读不加锁,迭代器基于快照,完全不检查modCount -
ConcurrentHashMap:分段/Node 级 CAS,迭代器弱一致性,不抛 CME -
ConcurrentLinkedQueue:无锁队列,迭代器不校验结构变更 - 手动同步 + 复制集合再遍历(适合小数据、低频场景)
这些方案放弃“快速失败”,换来了“安全运行”。Fail-Fast 和线程安全,从来就不是一回事。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











