← 返回题目列表

Iterator 的 fail-fast 和 fail-safe 是什么?

高频 困难 第 15 / 25 题 更新于 2026/07/28
Iteratorfail-fastfail-safe

简化版

fail-fast 指迭代过程中检测到集合被结构性修改时快速抛出 ConcurrentModificationException;fail-safe 指迭代时基于快照或弱一致性机制,不直接抛这个异常。ArrayList 常见迭代器是 fail-fast,CopyOnWriteArrayList 的迭代器更接近 fail-safe。

详细版

fail-fast 是一种快速失败机制。以 ArrayList 为例,集合维护 modCount,迭代器创建时保存 expectedModCount。遍历过程中如果发现两者不一致,说明集合被非迭代器方式结构性修改,就抛出 ConcurrentModificationException

fail-safe 通常不会直接遍历原集合的可变结构。例如 CopyOnWriteArrayList 迭代器基于创建时的数组快照,遍历时即使原集合修改,也不影响当前迭代器。

需要注意:fail-fast 不是线程安全保证,只是一种尽早暴露并发修改风险的检测机制。

完整版教学

一、什么是结构性修改

结构性修改通常指会改变集合大小或内部结构的操作,比如:

  • 添加元素。
  • 删除元素。
  • 清空集合。

单纯修改已有元素的字段,通常不算集合结构性修改。

例如:

for (String s : list) {
    list.remove(s);
}

这种写法容易触发 fail-fast,因为增强 for 背后使用迭代器,而循环内部直接修改了集合。

二、fail-fast 的基本原理

很多集合类中有一个修改计数 modCount。创建迭代器时,迭代器会记录当时的值。

遍历时,迭代器会检查:

if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
}

如果集合被外部修改,modCount 变了,而迭代器的 expectedModCount 没变,就会快速失败。

三、为什么 Iterator.remove 可以删除

用迭代器自己的 remove() 删除元素通常是允许的。

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.startsWith("A")) {
        it.remove();
    }
}

迭代器删除后,会同步更新自己的期望修改次数,使 expectedModCount 和集合 modCount 保持一致。

这也是面试里经常追问的点:遍历时删除元素,不要直接用集合的 remove(),要用迭代器的 remove()

四、fail-safe 是怎么回事

fail-safe 不是 Java 标准接口里的正式强制术语,但常用来描述“不因并发修改直接失败”的迭代方式。

典型例子是 CopyOnWriteArrayList。它写入时复制新数组,迭代器持有创建时的数组快照。

因此:

  • 遍历过程中不会抛 ConcurrentModificationException
  • 当前迭代器看不到创建之后的新修改。
  • 读多写少场景比较适合。

五、弱一致性迭代器

并发集合如 ConcurrentHashMap 的迭代器通常是弱一致性的。它不会抛 ConcurrentModificationException,遍历时可能看到部分并发修改,也可能看不到。

它不保证遍历结果是某个严格时刻的完整快照,但能在并发环境下安全遍历。

六、fail-fast 不是线程安全

很多人会误以为 fail-fast 可以保证并发安全。其实它只是检测到异常修改时尽快报错。

在并发环境下,它不能保证一定抛异常,也不能保证数据一致。真正需要线程安全时,应使用同步控制或并发集合。

七、常见误区与追问

fail-fast 是尽早暴露非预期结构修改的最佳努力机制,不是线程安全保证。ArrayList 迭代器保存 expectedModCount,若遍历期间集合通过其他路径结构性修改,使它与 modCount 不同,就可能抛 ConcurrentModificationException。CopyOnWriteArrayList 使用创建迭代器时的数组快照,因此遍历看不到之后新增的第4个元素。

检查维度判定依据
fail-fast检测到版本变化后尽快失败
快照/弱一致迭代允许并发变化,但可见性语义不同
expectedModCount != modCount -> ConcurrentModificationException

记忆钩子:fail-fast 是 bug 探测器,不是并发锁。

  • 误区:没有抛异常就证明没有并发修改。 检测是最佳努力,不能作为程序正确性的同步手段。
  • 追问:迭代器自身 remove 为什么通常不触发? 它会同步更新集合结构与 expectedModCount。
  • 误区:fail-safe 是 JDK Iterator 的正式统一术语。 更准确应说明快照迭代或弱一致迭代的具体容器语义。
  • 追问:CopyOnWrite 适合写多场景吗? 不适合,每次写会复制数组,适合读多写少。
  • 追问:ConcurrentHashMap 迭代会看到什么? 它是弱一致的,可能反映遍历期间的部分更新且不抛 CME。

八、加强记忆

fail-fast 是“发现被外部结构性修改就尽快报错”,fail-safe 是“通过快照或弱一致性避免直接报错”。前者帮助暴露错误用法,后者适合特定并发读场景,但都不能随便等同于绝对安全。