fail-fast
其實是一種系統設計的方法,維基百科上是這樣解釋的:java
在系統設計中,一個 fail-fast 的系統能夠經過特定的接口快速報告系統中任何潛在的故障。fail-fast 系統在發現故障時不會嘗試繼續運行系統,而會當即中止當前的操做。在進行一個操做時,會在多個檢查點檢查系統的狀態,因此出現故障能夠被儘早發現。fail-fast 模塊的職責就是檢查是否有故障,發現故障後會儘快通知上層系統來處理故障。編程
上面的文字看起來有點晦澀,實際的意思就是 fail-fast 是一種快速發現系統故障的機制,在檢測到系統狀態不對時,會當即中止當前的操做,讓上層的系統來處理這些故障。安全
與 fail-fast 相對的是 fail-safe。顧名思義,fail-safe 在故障發生以後會維持系統繼續運行。Java 在容器中用到了這兩種機制。微信
當使用迭代器(iterator)遍歷容器時,迭代器分爲兩種狀況,一種是 fail-fast,另外一種是 fail-sale。併發
fail-fast 在遍歷時,若是容器的元素被修改,就會報 ConcurrentModificationException 異常,而後終止遍歷。源碼分析
fail-safe 意味着在遍歷元素時,即便容器的元素被修改,不會拋出異常,也不會中止遍歷。post
本文基於 JDK1.8this
看以下的代碼:spa
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
Integer a = itr.next();
integers.remove(a);
}
複製代碼
上面使用 ArrayList 的代碼會報 ConcurrentModificationException 異常。線程
List<Integer> integers = new CopyOnWriteArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
Integer a = itr.next();
integers.remove(a);
}
複製代碼
而使用 CopyOnWriteArrayList 的代碼則不會報異常。
fail-fast 機制和 modCount 這個變量有關,這個變量會記錄容器被修改的次數,能夠理解爲容器對象的版本號。
那麼容器怎樣纔算是被修改呢?
須要注意,修改容器中元素的內容 modCount 不會增長
當容器使用迭代器對元素進行迭代時,會把 modCount 賦值給 expectedModCount。
int expectedModCount = modCount;
複製代碼
在迭代的過程當中,會不斷的去檢查 expectedModCount 與 modCount 的值是否相等,若是不相等,就說明在容器迭代的過程當中,有其餘的操做修改了容器,致使 modCount 的值增長,那麼就會報 ConcurrentModificationException 異常。
注:fail-fast 機制不單單在迭代器中使用了,容器的增刪改查和序列化等操做中也用到了。
有沒有辦法在迭代的過程當中刪除某些元素?使用迭代器自己的 remove 方法就行,這樣不會產生異常:
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
if (itr.next() == 2) {
itr.remove();
}
}
複製代碼
不會產生異常的緣由是在刪除元素後,把最新的 modCount 的值賦值給了 expectedModCount,代碼以下:
// ListItr.remove() method
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
複製代碼
fail-fast 機制能夠用來檢測容器元素是否被修改,可是須要注意的是,不能依賴 fail-fast 機制來保證容器的元素不被修改,也就是說,不要在沒有同步的狀況下併發的修改容器中的元素。fast-fast 機制原本的職責就是檢測系統的錯誤,因此僅僅只用它來檢測 bug,而不要做其餘的用途。
注:同步是併發編程中的一個術語,若是說一段代碼是同步的,那就表明是線程安全的
和 fail-fast 不一樣的是,使用了 fail-safe 的容器則能夠在迭代的過程當中任意的修改容器的元素而不會報錯。本質是由於迭代的是容器元素的副本,也就是說是將容器的元素拷貝了一份再進行遍歷,這樣即便原容器被修改,也不會影響到當前正在遍歷的元素。
CopyOnWriteArrayList 是一個支持 fail-safe 的容器,它獲取迭代器的代碼以下:
// CopyOnWriteArrayList.listIterator() method
Object[] es = getArray();
int len = es.length;
if (index < 0 || index > len)
throw new IndexOutOfBoundsException(outOfBounds(index, len));
return new COWIterator<E>(es, index);
複製代碼
// COWIterator inner class
private final Object[] snapshot;
private int cursor;
COWIterator(Object[] es, int initialCursor) {
cursor = initialCursor;
snapshot = es;
}
複製代碼
COWIterator 將容器的元素作了一個快照,後續的操做都是在這個快照上進行的。
爲了支持 fail-safe 特性,須要付出額外的代價:
java.util
包下的容器都有 fail-fast 機制,而java.util.concurrent
包下的容器則都是 fail-safe 的。
REF:
(完)
相關文章
關注微信公衆號,聊點其餘的