Fail-Fast 迭代器在集合结构修改时立即抛 ConcurrentModificationException,依赖 modCount 校验;Fail-Safe 则基于快照或弱一致性设计,不抛异常。前者用于 ArrayList 等非线程安全集合,后者用于 CopyOnWriteArrayList、ConcurrentHashMap 等线程安全集合。

Fail-Fast 和 Fail-Safe 是 Java 集合框架中两种截然不同的迭代器容错策略,核心区别在于:当集合在迭代过程中被结构修改(如 add/remove 元素),Fail-Fast 会立即抛出 ConcurrentModificationException,而 Fail-Safe 则不会抛异常,因为它操作的是原集合的副本或独立快照。
Fail-Fast 触发场景:基于 modCount 的实时校验
ArrayList、HashMap、HashSet 等非线程安全集合的迭代器采用 Fail-Fast 机制。其原理是在集合和迭代器中都维护一个 modCount(修改计数器),每次结构性修改(如 add()、remove()、clear())都会使 modCount 自增;迭代器每次调用 next() 或 remove() 前,都会检查自身保存的 expectedModCount 是否等于集合当前的 modCount,不一致即抛异常。
- 单线程下也会触发:例如在 for-each 循环中直接调用集合的
remove()方法 - 多线程下更常见:一个线程正在遍历,另一个线程并发修改了集合
- 注意:仅“结构性修改”触发,修改元素内容(如
list.set(i, x)或map.put(key, newVal))通常不改变 modCount,不触发 Fail-Fast
Fail-Safe 触发场景:迭代与修改互不影响
CopyOnWriteArrayList、ConcurrentHashMap(JDK 8+ 的 keySet/entrySet 迭代器)、ConcurrentLinkedQueue 等线程安全集合采用 Fail-Safe 机制。它们不依赖 modCount 校验,而是通过以下方式实现安全迭代:
- CopyOnWriteArrayList:迭代器持有的是创建时的数组快照,所有写操作(add/remove)都在新数组上进行,原快照不受影响
- ConcurrentHashMap:迭代器基于分段哈希表的当前状态构造,允许并发更新,迭代结果可能不反映最新修改(弱一致性),但绝不会抛 ConcurrentModificationException
- 注意:Fail-Safe 不代表“强一致性”,它牺牲实时性换安全性;迭代期间新增的元素对当前迭代器不可见
容易混淆的边界情况
有些操作看似安全,实则仍属 Fail-Fast 范畴:
- 使用
Iterator.remove()是合法的——该方法会同步更新expectedModCount,避免异常 - 使用
ListIterator.add()同样被支持,且会调整预期计数 -
forEach()(Stream API)底层仍可能使用普通迭代器,若源集合是 ArrayList,同样会触发 Fail-Fast -
removeIf()是集合自身的批量删除方法,内部已协调 modCount,不属于迭代中修改,不触发异常
如何选择与规避问题
没有绝对优劣,需按场景权衡:
- 读多写少 + 需要强一致性 → 用普通集合 + 外部同步(如
Collections.synchronizedList()),但注意迭代仍需手动同步 - 读远多于写 + 可接受最终一致性 → 优先选 CopyOnWriteArrayList(适合监听器列表等)或 ConcurrentHashMap
- 必须在遍历时删除元素 → 使用迭代器自身的
remove()方法,而非集合的remove() - 调试时遇到 ConcurrentModificationException → 检查是否在 for-each 或 while(iterator.hasNext()) 中调用了集合的修改方法

















