fail-fast
WikiPedia
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
在系统设计中,快速故障系统是一种立即在其接口上报告任何可能指示故障的情况的系统。快速故障系统通常旨在停止正常运行,而不是尝试继续可能存在缺陷的过程。这样的设计通常会在操作的几个点检查系统的状态,因此可以及早发现任何故障。快速故障模块的职责是检测错误,然后让系统的下一个最高级别处理错误。
fail-fast
(快速失败)可以理解为是一种系统机制,一种错误检测机制。在Java
集合框架中,如比较熟悉的ArrayList
,当多个线程访问ArrayList
实例时,其中一个线程或者多个线程对ArrayList
进行结构上的修改如add
或者remove
操作时会抛出ConcurrentModificationException
(并发修改异常)这种异常并不是每次都出现是随机的。
原理
ArrayList
有一个变量modCount
当对ArrayList
的结构上发上修改时会使modCount++
而当你利用Iterator
迭代器去遍历ArrayList
时会比较modCount!= expectedmodCount
在ArrayList
中有一个checkForComodification()
来检查期望的修改值是否和真实的修改值一致不一致就会抛出异常。以下代码如果将list.toString
注释掉程序是不会抛出异常的因为ArrayList
继承AbstractCollection,toString
方法是利用迭代器进行遍历,但是在迭代器中有对modCount
的判断所以导致程序会抛出异常,所以日常遍历ArrayList
的时推荐使用迭代器遍历,这样可以让程序正确的抛出异常。
补充
在遍历集合时失败的快速迭代器会立即抛出Concurrent Modification Exception
如果集合有结构上的修改。因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来的不确定时间内冒任意,不确定的行为的风险。
-
快速失败的迭代器可以在两种情况下引发
ConcurrentModificationException:
-
单线程环境
创建迭代器后,通过迭代器自己的remove
方法以外的任何方法随时修改结构。 -
多线程环境
如果一个线程正在修改集合的结构,而另一个线程正在其上进行迭代。
-
如何避免
- 在单线程的遍历过程中,如果要进行
remove
操作,可以调用迭代器的remove
方法而不是集合类的remove
方法。 - 使用
java
并发包java.util.concurrent
中的类来代替ArrayList
- 使用
Collections.synchronizedList()
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
new Thread(()->{
for(int i=0;i<10;i++) {
list.add(i);
}
}).start();
new Thread(()->{
list.remove(2);
}).start();
System.out.println(list.toString());
}
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
fail-safe
故障安全迭代器会复制内部数据结构(对象数组)并在复制的数据结构上进行迭代。对迭代器进行的任何结构修改都会影响复制的数据结构。因此,原始数据结构在结构上保持不变。因此,故障安全迭代器不会抛出ConcurrentModificationException
。
-
与故障安全迭代器相关的两个问题是:
-
1.维护复制的数据结构即内存的开销。
-
2.故障安全迭代器不保证所读取的数据是原始数据结构中当前的数据。
-
根据Oracle
文档,故障安全迭代器通常成本太高,但是在遍历操作远远超过变异时,其效率可能比替代方法高,并且在您无法或不想同步遍历而又需要防止并发线程之间的干扰时很有用。 “快照”样式的迭代器方法在创建迭代器时使用对数组状态的引用。该数组在迭代器的生命周期内永不更改,因此不会发生干扰,并且保证迭代器不会引发ConcurrentModificationException
。自创建迭代器以来,迭代器将不会反映对列表的添加,删除或更改。不支持在迭代器本身上进行元素更改操作(remove(),set()和add())
。这些方法引发UnsupportedOperationException
。
- 故障快速迭代器和故障安全迭代器的示例
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FailFastExample
{
public static void main(String[] args)
{
Map<String,String> premiumPhone = new HashMap<String,String>();
premiumPhone.put("Apple", "iPhone");
premiumPhone.put("HTC", "HTC one");
premiumPhone.put("Samsung","S5");
Iterator iterator = premiumPhone.keySet().iterator();
while (iterator.hasNext())
{
System.out.println(premiumPhone.get(iterator.next()));
premiumPhone.put("Sony", "Xperia Z");
}
}
}
- 输出
iPhone
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at FailFastExample.main(FailFastExample.java:20)
import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
public class FailSafeExample
{
public static void main(String[] args)
{
ConcurrentHashMap<String,String> premiumPhone =
new ConcurrentHashMap<String,String>();
premiumPhone.put("Apple", "iPhone");
premiumPhone.put("HTC", "HTC one");
premiumPhone.put("Samsung","S5");
Iterator iterator = premiumPhone.keySet().iterator();
while (iterator.hasNext())
{
System.out.println(premiumPhone.get(iterator.next()));
premiumPhone.put("Sony", "Xperia Z");
}
}
}
- 输出
S5
HTC one
iPhone
两者之间的不同
Fail Fast Iterator | Fail Safe Iterator | |
---|---|---|
Throw ConcurrentModification Exception | Yes | NO |
Clone object | NO | Yes |
Memory Overhead | Less | More |
Examples | HashMap,Vector,ArrayList,HashSet | CopyOnWriteArrayList,ConcurrentHashMap |