Fail-Safe 迭代 vs Fail-Fast 迭代
本文介绍Fail-Safe(安全失败) 和 Fail-Fast (快速失败)概念。
Fail-Safe(安全失败) 机制:尽可能立即暴露故障并停止整个操作。
Fail-Fast (快速失败) 机制: 失败情况下不中断操作,一些系统尝试尽量避免抛出失败异常。
Fail-Fast 迭代
当底层集合被修改时,java中Fail-Fast 迭代会触发。
集合维护内部计数器modCount,每次集合中增加或删除项,计数器会变化。当迭代时,每次调用next()方式时,当前modCount值与最初的modCount值进行比较。如果不匹配,抛出ConcurrentModificationException 异常,中断整个操作。
对java.util package 包中如 ArrayList, HashMap等默认是 Fail-Fast。
ArrayList<Integer> numbers = // ...
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
Integer number = iterator.next();
numbers.add(50);
}
上面代码片段中,在集合修改之后,当下一个next操作开始时会抛出ConcurrentModificationException 异常。
Fail-Fast行为不保证在所有场景都发生,因为并发修改情况下行为不可预测。这些迭代器尽最大努力抛出ConcurrentModificationException 异常。
如果在迭代集合时,通过迭代器的remove方法删除项,是安全的,不会抛出异常。反之,通过集合remove方法删除项则会抛出异常:
ArrayList<Integer> numbers = // ...
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 30) {
iterator.remove(); // ok!
}
}
iterator = numbers.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 40) {
numbers.remove(2); // exception
}
}
Fail-Safe 迭代
Fail-Safe 迭代支持在异常处理不方便时不抛出异常。这些迭代器创建实际集合克隆,基于克隆集合进行迭代。如果迭代器创建之后有任何修改,拷贝版本仍保留未修改的集合。因此,即使集合被修改了,迭代器继续在克隆集合上循环。
然而,需要特别说明的是,没有真正的Fail-Safe 迭代,正确术语应该是弱一致性。这意味着,如果在迭代过程中对集合进行了修改,那么迭代器所看到的是弱保证的。对于不同的集合,这种行为可能是不同的,可以查阅每个集合类的Javadocs。
然而Fail-Safe 迭代有一些缺点,首先是iterator不能保证返回集合更新后的数据。因为其工作在集合克隆上,而非集合本身。其次,创建集合拷贝需要相应的开销,包括时间和内存。
在java.util.concurrent 包中集合迭代器,如 ConcurrentHashMap, CopyOnWriteArrayList等默认为 Fail-Safe。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("First", 10);
map.put("Second", 20);
map.put("Third", 30);
map.put("Fourth", 40);
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
map.put("Fifth", 50);
}
上面代码片段中,我们使用了Fail-Safe 迭代,因此,即使在迭代过程中集合元素增加了,不会抛出异常。
ConcurrentHashMap的默认迭代器是弱一致的。这意味着这个迭代器可以容忍并发的修改,在迭代器构建时遍历元素,并且可以(但不能保证)在迭代器构建之后反映对集合的修改。
因此,在上面的代码片断中,迭代循环5次,这意味着它确实检测到新添加的元素到集合中。
总结
本文介绍Fail-Safe(安全失败) 和 Fail-Fast (快速失败)概念,以及它们是如何在Java中实现的。