fail-safe机制核心是读写分离,迭代器基于创建时的独立快照副本遍历,不访问原集合实时状态,故不抛ConcurrentModificationException;典型实现如CopyOnWriteArrayList通过写时复制保障线程安全。

fail-safe 机制的核心是“读写分离”——迭代器不访问原始集合的实时状态,而是基于某个时间点的独立副本进行遍历,因此即使其他线程正在修改原集合,也不会触发 ConcurrentModificationException,也不会破坏遍历过程。
快照副本是遍历唯一数据源
fail-safe 迭代器在创建时(如调用 iterator() 方法)会立即生成一份完整、不可变的集合副本。这个副本通常是一个数组(如 CopyOnWriteArrayList 的 getArray() 返回值)或序列化后的本地结构(如 Redisson 的 RList.iterator() 拉取全量数据)。后续所有 next()、hasNext() 操作都只从该副本读取,与原集合完全解耦。
- 原集合的
add、remove、clear等操作不会影响已创建的迭代器 - 副本一旦生成即固定,不会随原集合更新而更新
- 这意味着迭代器看到的是“过去某一刻”的视图,不是实时数据
写操作通过复制+替换实现无锁隔离
支持 fail-safe 的集合(如 CopyOnWriteArrayList)在执行写操作时,并非直接修改原底层数组,而是:
- 先复制当前整个数组
- 在副本上完成新增、删除或修改
- 最后通过 volatile 引用或 CAS 原子替换底层数组引用
由于旧迭代器持有的仍是原数组引用,新旧读写自然隔离。这种“写时复制(Copy-On-Write)”策略牺牲了写性能和内存,但换来了读操作的绝对安全与无锁并发能力。
立即学习“Java免费学习笔记(深入)”;
不同场景下的 fail-safe 实现有本质差异
不能把所有“不抛异常”的迭代器都简单等同于 fail-safe。关键要看是否真有副本:
-
CopyOnWriteArrayList:典型 fail-safe,本地数组快照,强隔离 -
ConcurrentHashMap:迭代器不抛异常,但并非靠快照——它利用分段锁 + volatile 读 + 多次重试,属于弱一致性(weakly consistent),可能看到部分新写入,也可能跳过某些更新 -
ConcurrentSkipListSet:同样不抛异常,但底层是跳表结构 + volatile 节点指针 + 逻辑删除标记,遍历中动态导航,也不复制数据 - Redisson 分布式集合(如
RList):调用iterator()时一次性从 Redis 拉取全部元素构建本地不可变快照,网络开销大,但行为表现类似 fail-safe
适用场景与使用注意
fail-safe 不是万能方案,它有明确的设计取舍:
- 适合读远多于写的场景(如监听器列表、配置缓存、事件广播)
- 不适合要求强一致性的实时数据展示(例如后台管理界面需即时反映增删)
- 大数据量下内存压力明显(每次写都复制整个结构)
- 遍历时无法感知自己刚添加的元素,也无法保证跳过已被其他线程删除的元素(只要它还在快照里)


















