Java容器系列-Fail-Fast機制到底是什麼

Rayjun發表於2019-12-21

fail-fast 實際上是一種系統設計的方法,維基百科上是這樣解釋的:

在系統設計中,一個 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 意味著在遍歷元素時,即使容器的元素被修改,不會丟擲異常,也不會停止遍歷。

本文基於 JDK1.8

fail-fast 具體實現

看如下的程式碼:

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:

(完)

原文

相關文章

關注微信公眾號,聊點其他的

Java容器系列-Fail-Fast機制到底是什麼

相關文章