fail-fast與fail-safe簡介
如果一個系統,當有異常或者錯誤發生時就立即中斷執行,這種設計稱之爲fail-fast
。相反如果我們的系統可以在某種異常或者錯誤發生時繼續執行,不會被中斷,這種設計稱之爲fail-safe
。
fail-fast與fail-safe在Java迭代器中的設計
在Java中,最典型的fail-fast與fail-safe就是關於迭代器的設計。通常情況下,那些線程不安全的集合類產生的迭代器都是fail-fast的,而線程安全的集合類產生的迭代器是fail-safe的。fail-fast的迭代器會在迭代過程中,如果你修改了集合類裏的內容,則會拋出ConcurrentModificationException
異常。fail-safe的迭代器則可以在迭代過程中任意修改集合類的內容,不會有異常拋出。
Java的fail-fast迭代器
前面說過,線程安全的集合類產生的迭代器是基於fail-fast設計的,例如ArrayList
。這種迭代器迭代過程中是直接訪問原數據信息的,所以當原集合內容修改了後,迭代器不能保證正確的迭代過程。代碼示例如下:
public static void failFast() {
List<String> list = new ArrayList<>();
list.add("item-1");
list.add("item-2");
list.add("item-3");
list.add("item-4");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
System.out.println(item);
list.add("itme-5"); // 下次迭代時會拋出ConcurrentModificationException異常
}
}
通過分析ArrayList
源碼可以看出,當對ArrayList
做添加或者刪除元素的操作時,都會修改modCount
這個變量,而ArrayList
的迭代器每次迭代的時候,又都回去檢查當前modCount
和迭代器產生時的expectedModCount
變量是否相等,如果不等就會拋出ConcurrentModificationException
異常。
protected transient int modCount = 0;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 上面那個方法調用後會修改modCount
....
}
// ArrayList的迭代器
private class Itr implements Iterator<E> {
public E next() {
checkForComodification();
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
...
}
Java的fail-safe迭代器
對於那些線程安全的集合類,在調用iterator
方法產生迭代器的時候,會將當前集合的素有元素都做一個快照,即複製一份副本。每次迭代的時候都是訪問這個快照內的元素,而不是原集合的元素。代碼示例如下:
public static void failSafe() {
List<String> list = new CopyOnWriteArrayList<>();
list.add("item-1");
list.add("item-2");
list.add("item-3");
list.add("item-4");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
System.out.println(item);
list.add("itme-5");
}
System.out.println(list.size()); // 會打印出來8,迭代四次,四個新元素插入到了集合中。
}
這種設計的好處是保證了在多線程操縱同一個集合的時候,不會因爲某個線程修改了集合,而影響其他正在迭代訪問集合的線程。缺點是,迭代器不能正確及時的反應集合中的內容,而且一定程度上也增加了內存的消耗。
迭代器小提示
如果用Java的for loop
來訪問集合,原理上還是用迭代器的方式,所以下面的代碼同樣會拋出ConcurrentModificationException
異常。
List<String> list = new ArrayList<>();
list.add("item-1");
list.add("item-2");
list.add("item-3");
list.add("item-4");
for (String item : list) {
System.err.println(item);
list.add("itme-5");
}
fail-safe與fail-fast的區別
當我們對集合結構上做出改變的時候,fail-fast機制就會拋出異常。但是,對於採用fail-safe機制來說,就不會拋出異常(大家估計看到safe兩個字就知道了)。
這是因爲,當集合的結構被改變的時候,fail-safe機制會在複製原集合的一份數據出來,然後在複製的那份數據遍歷。
因此,雖然fail-safe不會拋出異常,但存在以下缺點
- 複製時需要額外的空間和時間上的開銷。
- 不能保證遍歷的是最新內容。