Fail-Fast依赖modCount检测并发修改并立即抛ConcurrentModificationException,Fail-Safe通过快照或并发结构隔离读写,不校验modCount,避免异常但数据弱一致。

Fail-Fast 和 Fail-Safe 的核心差异,就藏在 modCount 这个变量里——它不是用来“保证线程安全”的,而是专为“暴露错误”而生的检测标记。
modCount 是什么?它怎么被修改?
modCount(modification count)是 Java 集合(如 ArrayList、HashMap)中定义在父类 AbstractList 或 AbstractMap 中的一个 int 类型字段,用于记录集合结构被修改的次数。
所谓“结构修改”,指的是影响集合大小或内部组织的操作,例如:
-
add()、remove()、clear() -
put()触发 HashMap 扩容时 -
retainAll()、removeAll()等批量操作
注意:set()(仅替换元素值,不改变 size 或结构)通常 不会 修改 modCount;而迭代器自身的 remove() 方法会同步更新预期值,因此不会触发异常。
Fail-Fast 如何靠 modCount 检测“非法修改”?
Fail-Fast 不是防御机制,而是调试哨兵。它的检测逻辑非常直接:
- 创建迭代器时,将当前集合的
modCount值复制给迭代器内部的expectedModCount - 每次调用
next()或hasNext()前,都执行checkForComodification() - 该方法只做一件事:
if (modCount != expectedModCount) throw new ConcurrentModificationException();
这意味着:只要你在迭代过程中用集合自身的方法增删元素,modCount 就会变,而迭代器并不知情,下次取元素前一比对,立刻失败。哪怕单线程也逃不掉——它不区分“谁改的”,只认“有没有改”。
Fail-Safe 为什么绕开了 modCount 检查?
Fail-Safe 集合(如 CopyOnWriteArrayList、ConcurrentHashMap)根本不依赖 modCount 做运行时校验。它们的策略是彻底隔离读写:
-
CopyOnWriteArrayList:迭代器持有的是构造时刻的数组快照,后续所有add/remove都在新数组上操作,原快照不变 →modCount对迭代器无意义 -
ConcurrentHashMap:采用分段锁 + CAS,迭代器遍历的是某一时点的 Segment 状态,不检查全局 modCount,也不阻塞写入
所以,Fail-Safe 不是“更聪明地检测”,而是“放弃检测,换一种不冲突的实现方式”。这也带来代价:内存占用高、读到的数据可能滞后。
常见误区与关键提醒
别把 ConcurrentModificationException 当作并发控制手段:
- 它不能保证一定抛出:极端情况下(如先删后加,modCount 恰好回绕一致),异常可能漏报
- 它不是线程安全的替代品:抛异常 ≠ 阻止了数据错乱,只是让你知道“这里可能已经乱了”
- 增强 for 循环(
for (e : list))底层就是迭代器,同样受 modCount 约束 - JUC 包下的并发集合(
java.util.concurrent)基本都是 Fail-Safe;而java.util下的传统集合默认 Fail-Fast










