在 Java 中,fail-fast
(快速失败)和 fail-safe
(安全失败)是两种在集合类进行遍历或修改时的异常处理机制。它们核心区别在于:是否允许在遍历过程中结构被修改,以及是否会抛出 ConcurrentModificationException
。
✅ 一、fail-fast(快速失败)
📌 定义:
当集合在被遍历期间结构发生变化(非当前迭代器自身的 remove),则会立即抛出 ConcurrentModificationException
异常。
🧠 原理:
fail-fast 机制通过一个modCount 结构修改计数器来实现。
-
每当集合结构发生变化(add/remove),modCount++
-
迭代器创建时,会记录一个期望值 expectedModCount = modCount
-
遍历过程中,每次 next()/hasNext() 都会检查:
if (modCount != expectedModCount) throw new ConcurrentModificationException();
❗适用于哪些集合?
-
ArrayList
,HashMap
,HashSet
,LinkedList
等大多数 JDK 中的常规集合类
❌ 问题场景:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String s : list) {
list.remove(s); // 会抛 ConcurrentModificationException
}
✅ 二、fail-safe(安全失败)
📌 定义:
遍历期间即使集合发生结构变化,也不会抛异常,能够容忍并发修改,但修改对当前迭代器不可见(即“弱一致性”)。
🧠 原理:
fail-safe 迭代器不直接在原始集合上操作,而是复制一份快照(CopyOnWrite)或者使用弱一致的底层数据结构(如跳表)。
✅ 常见实现:
实现类 | 机制 | 特性 |
---|---|---|
CopyOnWriteArrayList | 写时复制 | 遍历时快照,线程安全,适合读多写少 |
ConcurrentHashMap | 分段锁 / CAS + bin 锁 | 支持并发修改,遍历弱一致 |
ConcurrentSkipListMap | 跳表结构 | 排序、并发安全 |
✅ 示例:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
for (String s : list) {
list.remove(s); // 不会抛异常,安全失败
}
✅ 三、对比总结
特性 | Fail-Fast | Fail-Safe |
---|---|---|
迭代结构变化响应 | 抛出 ConcurrentModificationException | 不抛异常,允许修改 |
线程安全 | ❌ 否(多数为非线程安全集合) | ✅ 是(基于 CopyOnWrite 或 CAS) |
实现方式 | 检查 modCount | 快照副本或弱一致性容器 |
修改是否可见 | ❌ 修改直接失败 | ❌ 修改对当前迭代器不可见(快照)或延迟可见 |
性能影响 | 遍历性能好,但不容错 | 写性能差(CopyOnWrite),适合读多写少 |
✅ 四、使用建议
场景 | 建议容器 / 机制 |
---|---|
高并发读写 | ConcurrentHashMap |
多线程读多写少,且遍历频繁 | CopyOnWriteArrayList |
单线程或内部控制修改 | 普通集合 + fail-fast 机制 |
避免 fail-fast 异常 | 使用显式 Iterator.remove() |
是否需要我再补充 CopyOnWriteArrayList
和 ConcurrentHashMap
的 fail-safe 原理源码细节?