fail-fast和fail-safe的介紹和區別
阿新 • • 發佈:2019-01-08
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();
}
原理
:
- 迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個
modCount
變數, - 集合中在被遍歷期間如果內容發生變化,就會改變
modCount
- 每當迭代器使用
hashNext()
/next()
遍歷下一個元素之前,都會檢測modCount
變數和expectedmodCount
值是否相等, - 如果相等就返回遍歷,否則丟擲異常,終止遍歷.
舉例
//會丟擲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包下的容器都是安全失敗的,可以在多執行緒下併發使用,併發修改.