Java容器系列-Fail-Fast機制究竟是什麼

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

fail-fast 具體實現

看以下的代碼: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-safe 具體實現

和 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:

(完)

原文

相關文章

關注微信公衆號,聊點其餘的

相關文章
相關標籤/搜索