快速失败(fail-fast)和安全失败(fail-safe)的区别
在 Java 中,快速失败(Fail-Fast)和安全失败(Fail-Safe)是集合类(Collection)在迭代过程中处理并发修改的两种不同策略,二者的核心区别在于 对并发修改的感知机制与容错性。
一、 快速失败(Fail-Fast)
-
核心特性:
一旦检测到集合在迭代过程中被修改(除通过迭代器自身的方法),立即抛出ConcurrentModificationException
,终止程序。 -
实现原理:
- 迭代器内部维护一个
modCount
计数器,记录集合的修改次数。 - 每次迭代时检查
modCount
是否与初始值一致,若不一致,说明集合被修改,触发异常。
- 迭代器内部维护一个
-
适用场景:
单线程环境,或明确确保不会在迭代时修改集合的场景。 -
示例集合:
ArrayList
、HashMap
、HashSet
(非线程安全集合的默认行为)。 -
代码演示:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();list.remove(s); // 直接操作原集合:抛出 ConcurrentModificationException
}
二、安全失败(Fail-Safe)
-
核心特性:
允许在迭代过程中修改集合(如添加、删除元素),不会抛出异常。迭代器基于集合的副本或快照进行遍历,与原集合解耦。 -
实现原理:
- 迭代器操作的是集合的副本(如
CopyOnWriteArrayList
的写时复制技术)。 - 或通过并发控制(如
ConcurrentHashMap
的分段锁机制)保证线程安全。
- 迭代器操作的是集合的副本(如
-
适用场景:
多线程环境,或需要在迭代过程中修改集合的场景。 -
示例集合:
CopyOnWriteArrayList
、ConcurrentHashMap
、ConcurrentLinkedQueue
。 -
代码演示:
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();list.add("D"); // 直接操作原集合:不会抛出异常
}
System.out.println(list); // 输出 [A, B, C, D, D, D]
三、关键区别总结
特性 | 快速失败(Fail-Fast) | 安全失败(Fail-Safe) |
---|---|---|
并发修改检测 | 立即抛出异常,强制终止程序 | 允许修改,不触发异常 |
迭代器数据源 | 直接操作原集合 | 基于集合的副本或快照 |
性能开销 | 低(仅计数器检查) | 高(需维护副本或同步控制) |
数据一致性 | 强一致性(反映最新状态) | 弱一致性(可能遍历旧数据) |
典型集合 | ArrayList , HashMap | CopyOnWriteArrayList , ConcurrentHashMap |
四、选择建议
- 若需 强一致性 且确定无并发修改 → Fail-Fast。
- 若需 高并发支持 或允许弱一致性 → Fail-Safe。
例如:
- 单线程场景优先使用
ArrayList
(Fail-Fast); - 多线程高并发场景选择
ConcurrentHashMap
(Fail-Safe)。
五、Fail-Safe的使用场景
Fail-Safe(安全失败)机制在多线程或动态修改集合的场景中尤为重要,以下是典型使用场景及示例:
(1)多线程并发操作
当多个线程同时读写集合时,Fail-Safe集合(如 ConcurrentHashMap
)可避免因并发修改导致的异常。
示例:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);// 线程1:迭代
new Thread(() -> {Iterator<String> it = map.keySet().iterator();while (it.hasNext()) {System.out.println(it.next()); // 安全遍历}
}).start();// 线程2:修改
new Thread(() -> {map.put("C", 3); // 不会触发异常
}).start();
(2)动态数据更新
在需要实时更新数据的场景(如实时监控系统),CopyOnWriteArrayList
允许遍历时修改数据。
示例:
CopyOnWriteArrayList<String> logList = new CopyOnWriteArrayList<>();
logList.add("Log1");// 遍历时追加日志
for (String log : logList) {System.out.println(log);logList.add("NewLog"); // 安全操作
}
(3)高吞吐量系统
如消息队列消费者处理数据时,Fail-Safe集合可避免因频繁修改导致的迭代中断。