快速失敗(fail-fast)與安全失敗(fail-safe)

fail-fast與fail-safe

在Collection集合的各個類中,有線程安全和線程不安全這2大類的版本。java

對於線程不安全的類,併發狀況下可能會出現fail-fast狀況;而線程安全的類,可能出現fail-safe的狀況。數組

1、併發修改

當一個或多個線程正在遍歷一個集合Collection的時候(Iterator遍歷),而此時另外一個線程修改了這個集合的內容(如添加,刪除或者修改)。這就是併發修改的狀況。安全

2、fail-fast快速失敗

fail-fast機制:當遍歷一個集合對象時,若是集合對象的結構被修改了,就會拋出ConcurrentModificationExcetion異常。多線程

有2種狀況會拋出該異常:併發

  1. 在單線程的狀況下,若是使用Iterator對象遍歷集合對象的過程當中,修改了集合對象的結構。以下:spa

    // 1.iterator迭代,拋出ConcurrentModificationException異常
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      String s = iterator.next();
      System.out.println(s);
      // 修改集合結構
      if ("s2".equals(s)) {
        list.remove(s);
      }
    }
    
    // 2.foreach迭代,拋出ConcurrentModificationException異常
    for (String s : list) {
      System.out.println(s);
      // 修改集合結構
      if ("s2".equals(s)) {
        list.remove(s);
      }
    }

    要想避免拋出異常,應該使用Iterator對象的remove()方法。線程

    // 3.iterator迭代,使用iterator.remove()移除元素不會拋出異常
    Iterator<String> iterator2 = list.iterator();
    while (iterator2.hasNext()) {
      String s = iterator2.next();
      System.out.println(s);
      // 修改集合結構
      if ("s2".equals(s)) {
      iterator2.remove();
      }
    }
  2. 在多線程環境下,若是對集合對象進行併發修改,那麼就會拋出ConcurrentModificationException異常。

注意,迭代器的快速失敗行爲沒法獲得保證,由於通常來講,不可能對是否出現不一樣步併發修改作出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。所以,爲提升這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的作法,迭代器的快速失敗行爲應該僅用於檢測 bug。code

以ArrayList爲例,講解一下fail-fast的機制

一、單線程下,使用iterator迭代時的狀況

ArrayList繼承自AbstractList類,AbstractList內部有一個字段modCount,表明修改的次數。對象

fail-1

ArrayList類的add、remove操做都會使得modCount自增。blog

fail-2

fail-3

當使用ArrayList.iterator()返回一個迭代器對象時。迭代器對象有一個屬性expectedModCount,它被賦值爲該方法調用時modCount的值。這意味着,這個值是modCount在這個時間點的快照值,expectedModCount值在iterator對象內部不會再發送變化!

fail-4

這時候咱們就能明白了,在獲得迭代器以後,若是咱們使用ArrayList的add、remove等方法,會使得modCount的值自增(發生了變化),而iterator內部的expectedModCount值卻仍是以前的快照值。咱們再來看iterator的方法實現:能夠看到,在調用next方法時,第一步就是檢查modCount值和迭代器內部的expectedModCount值是否相等!顯然,這是不等的,因此在調用next方法的時候,就拋出了ConcurrentModificationException異常。

fail-5

fail-6

爲何說迭代器的fail-fast機制是盡最大努力地拋出ConcurrentModificationException異常呢?

緣由就是上面咱們看到的,只有在迭代過程當中修改了元素的結構,當再調用next()方法時纔會拋出該異常。也就是說,若是迭代過程當中發生了修改,但以後沒有調用next()迭代,該異常就不會拋出了!(該異常的機制是告訴你,當前迭代器要進行操做是有問題的,由於集合對象如今的狀態發生了改變!)

那爲何iterator.remove()方法可行呢?

下圖中,能夠看到,remove方法沒有進行modCount值的檢查,而且手動把expectedModCount值修改爲了modCount值,這又保證了下一次迭代的正確。

fail-7

二、多線程下的狀況

固然,若是多線程下使用迭代器也會拋出ConcurrentModificationException異常。而若是不進行迭代遍歷,而是併發修改集合類,則可能會出現其餘的異常如數組越界異常。

3、fail-safe安全失敗

Fail-Safe 迭代的出現,是爲了解決fail-fast拋出異常處理不方便的狀況。fail-safe是針對線程安全的集合類。

上面的fail-fast發生時,程序會拋出異常,而fail-safe是一個概念,併發容器的併發修改不會拋出異常,這和其實現有關。併發容器的iterate方法返回的iterator對象,內部都是保存了該集合對象的一個快照副本,而且沒有modCount等數值作檢查。以下圖,這也形成了併發容器的iterator讀取的數據是某個時間點的快照版本。你能夠併發讀取,不會拋出異常,可是不保證你遍歷讀取的值和當前集合對象的狀態是一致的!這就是安全失敗的含義。

fail-8

因此Fail-Safe 迭代的缺點是:首先是iterator不能保證返回集合更新後的數據,由於其工做在集合克隆上,而非集合自己。其次,建立集合拷貝須要相應的開銷,包括時間和內存。

在java.util.concurrent 包中集合的迭代器,如 ConcurrentHashMap, CopyOnWriteArrayList等默認爲都是Fail-Safe。

// 1.foreach迭代,fail-safe,不會拋出異常
for (String s : list) {
  System.out.println(s);
  if ("s1".equals(s)) {
  list.remove(s);
  }
}

// 2.iterator迭代,fail-safe,不會拋出異常
Iterator<String> iterator = list.iterator();
  while (iterator.hasNext()) {
  String s = iterator.next();
  System.out.println(s);
  if ("s1".equals(s)) {
  list.remove(s);
  }
}
相關文章
相關標籤/搜索