fail-safe机制通过创建集合快照实现遍历安全,迭代器操作独立副本,避免ConcurrentModificationException;典型实现为CopyOnWriteArrayList,适用于读多写少场景。

Java 集合中的 fail-safe 机制通过“不读原集合、只读快照”来避免 ConcurrentModificationException,本质是用空间换安全——遍历时与原集合完全解耦。
核心原理:迭代器持有独立副本
fail-safe 迭代器在创建时(如调用 iterator()),会立即对当前集合做一次完整复制(例如调用 toArray() 或新建 ArrayList),生成一个不可变的快照数组或列表。后续所有 next()、hasNext() 操作都只访问这个副本,和原集合的任何增删改操作彻底隔离。
- 原集合执行
add、remove、clear,只影响自身底层数组引用,不会波及已存在的快照 - 多个迭代器可同时遍历,彼此互不影响,也无需加锁
- 快照一旦生成即固定,内存中存在两份数据(原集合 + 副本),写操作开销大但读操作零同步成本
典型实现类:CopyOnWriteArrayList / CopyOnWriteArraySet
这是 JDK 中最标准的 fail-safe 实践。以 CopyOnWriteArrayList 为例:
- 每次写操作(
add、set、remove)都会先复制整个底层数组,在副本上修改,再用 volatile 引用原子替换原数组 -
iterator()返回的迭代器,其内部持有的是调用时刻的数组引用,后续无论原数组如何更新,它始终遍历旧数据 - 因此即使遍历中途另一线程添加了元素,当前迭代器也完全感知不到,自然不会校验失败、更不会抛异常
使用注意事项:快照 ≠ 实时视图
fail-safe 的代价是牺牲实时一致性:
立即学习“Java免费学习笔记(深入)”;
- 迭代期间新增的元素不会出现在本次遍历结果中
- 已被原集合删除的元素,只要它存在于快照里,仍会被遍历到
- 不支持
Iterator.remove()(直接抛UnsupportedOperationException),因为快照不可变 - 适合读多写少场景,比如事件监听器列表、配置项缓存、白名单校验等
手动模拟 fail-safe(仅用于理解)
虽然不推荐生产使用,但可直观体现逻辑:
List<String> original = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> snapshot = new ArrayList<>(original); // 关键:立刻复制
Iterator<String> it = snapshot.iterator();
original.add("d"); // 对 it 完全无影响
while (it.hasNext()) System.out.println(it.next()); // 输出 a b c


















