
快速失败(fail-fast) 和 安全失败(fail-safe) 是两种不同的迭代器设计策略,主要用于处理集合(如 List
、Map
)在遍历过程中被修改的场景。
它们的核心区别在于对并发修改的容忍度和实现机制。
1. 快速失败(fail-fast)
定义
快速失败是一种 "零容忍" 的机制。当集合在遍历过程中被修改(如添加、删除元素),迭代器会立即抛出 ConcurrentModificationException
,强制终止操作。
实现原理
- 集合内部维护一个
modCount
(修改计数器)。 - 每次对集合进行结构性修改(如
add
、remove
),modCount
递增。 - 迭代器初始化时记录当前的
modCount
。 - 每次遍历时检查
modCount
是否与初始值一致。若不一致,抛出异常。
特点
- 立即失败:只要检测到修改,直接终止程序。
- 适用于单线程:多线程环境下无法可靠检测并发修改。
- 常见集合 :
ArrayList
、HashMap
、HashSet
(非线程安全集合)。
示例
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
list.add("D"); // 修改集合的modCount
it.next(); // 遍历时检查到modCount变化 → 抛出ConcurrentModificationException
2. 安全失败(fail-safe)
定义
安全失败是一种 "容忍修改" 的机制。即使集合在遍历过程中被修改,迭代器也不会抛出异常,而是基于原始数据或快照继续遍历。
实现原理
- 迭代器操作的是集合的 副本 或 快照,而非原始数据。
- 修改操作对当前迭代器的遍历无影响。
- 牺牲数据一致性以换取安全性。
特点
- 允许并发修改:不会抛出异常,但可能无法看到最新数据。
- 适用于多线程:通过快照或弱一致性设计支持并发操作。
- 常见集合 :
ConcurrentHashMap
、CopyOnWriteArrayList
(线程安全集合)。
示例
java
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
list.add("D"); // 修改集合,但迭代器基于快照遍历
it.forEachRemaining(System.out::print); // 输出"A B C",不包含"D"
3. 核心对比
特性 | 快速失败(fail-fast) | 安全失败(fail-safe) |
---|---|---|
对修改的响应 | 立即抛出异常,终止遍历 | 忽略修改,继续遍历 |
数据一致性 | 严格反映最新数据(但可能抛出异常) | 可能基于快照,不反映最新修改 |
适用场景 | 单线程环境 | 多线程环境 |
性能开销 | 低(仅计数器检查) | 高(需维护副本或快照) |
典型集合 | ArrayList 、HashMap |
ConcurrentHashMap 、CopyOnWriteArrayList |
4. 应用场景
- 快速失败 :
- 适合单线程快速检测代码逻辑错误(如意外修改集合)。
- 示例:开发阶段调试集合的非法修改。
- 安全失败 :
- 适合高并发场景(如实时数据处理、消息队列)。
- 示例:多线程环境下遍历
ConcurrentHashMap
。
5. 注意事项
- 快速失败的局限性 :
- 通过
modCount
检测修改,但并非所有修改都能被捕获(如通过迭代器的remove
方法)。
- 通过
- 安全失败的一致性代价 :
- 迭代器可能无法反映最新数据(如
CopyOnWriteArrayList
的写时复制机制)。
- 迭代器可能无法反映最新数据(如
总结
- 快速失败 :强调 "即时报错",适用于单线程快速发现问题。
- 安全失败 :强调 "稳定运行",适用于多线程高并发场景。
- 选择策略:根据线程安全和数据一致性需求选择合适的集合实现。