1. 程式人生 > >fail-fast和fail-safe的介紹和區別

fail-fast和fail-safe的介紹和區別

fail-fast和fail-safe

前言

前段時間公司招的實習生在使用迭代器遍歷的時候,對集合內容進行了修改,從而丟擲ConcurrentModificationException. 然後給他講解之餘也整理了這一篇文章.

fail-fast ( 快速失敗 )

  • 在使用迭代器遍歷一個集合物件時,比如增強for,如果遍歷過程中對集合物件的內容進行了修改(增刪改),會丟擲 ConcurrentModificationException 異常.

  • 檢視ArrayList原始碼,在next方法執行的時候,會執行checkForComodification()方法

@SuppressWarnings
("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1
; return (E) elementData[lastRet = i]; } //...............省略............. final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

原理:

  1. 迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個modCount變數,
  2. 集合中在被遍歷期間如果內容發生變化,就會改變modCount
    的值,
  3. 每當迭代器使用 hashNext()/next()遍歷下一個元素之前,都會檢測modCount變數和expectedmodCount值是否相等,
  4. 如果相等就返回遍歷,否則丟擲異常,終止遍歷.

舉例

//會丟擲ConcurrentModificationException異常
for(Person person : Persons){
    if(person.getId()==2)
        student.remove(person);
}

注意

這裡異常的丟擲條件時檢測到modCount = expectedmodCount 這個條件.

如果集合發生變化時修改modCount值, 剛好有設定為了expectedmodCount值, 則異常不會丟擲.(比如刪除了資料,再新增一條資料)

//不會丟擲ConcurrentModificationException異常
for(Person person : Persons){
    if(person.getId()==2){
        Persons.remove(person);
        Persons.add(new Person());
    }
}

所以不能依賴於這個異常是否丟擲而進行併發操作的程式設計, 這個異常只建議檢測併發修改的bug.

使用場景 :

java.util包下的集合類都是快速失敗機制的, 不能在多執行緒下發生併發修改(迭代過程中被修改).

fail-safe ( 安全失敗 )

採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先copy原有集合內容,在拷貝的集合上進行遍歷.

原理:

  • 由於迭代時是對原集合的拷貝的值進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會出發ConcurrentModificationException

缺點:

  • 基於拷貝內容的優點是避免了ConcurrentModificationException,但同樣地, 迭代器並不能訪問到修改後的內容 (簡單來說就是, 迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的)

使用場景:

java.util.concurrent包下的容器都是安全失敗的,可以在多執行緒下併發使用,併發修改.