1. 程式人生 > >基礎概念:fail-fast(快速失敗) 與 fail-safe(安全失敗) 機制有什麼區別

基礎概念:fail-fast(快速失敗) 與 fail-safe(安全失敗) 機制有什麼區別

在我們詳細討論這兩種機制的區別之前,首先得先了解併發修改。

1.什麼是併發(同步)修改?

當一個或多個執行緒正在遍歷一個集合Collection,此時另一個執行緒修改了這個集合的內容(新增,刪除或者修改)。這就是併發修改。

2.什麼是 fail-fast 機制?

fail-fast機制在遍歷一個集合時,當集合結構被修改,會丟擲Concurrent Modification Exception。
fail-fast會在以下兩種情況下拋Concurrent Modification Exception
(1)單執行緒環境
集合被建立後,在遍歷它的過程中修改了結構。
注意 remove()方法會讓expectModcount和modcount 相等,所以是不會丟擲這個異常。
(2)多執行緒環境


當一個執行緒在遍歷這個集合,而另一個執行緒對這個集合的結構進行了修改。

注意:迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力丟擲 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤的做法:迭代器的快速失敗行為應該僅用於檢測 bug。

3. fail-fast機制是如何檢測的?

迭代器在遍歷過程中是直接訪問內部資料的,因此內部的資料在遍歷的過程中無法被修改。為了保證不被修改,迭代器內部維護了一個標記 “mode” ,當集合結構改變(新增刪除或者修改),標記"mode"會被修改,而迭代器每次的hasNext()和next()方法都會檢查該"mode"是否被改變,當檢測到被修改時,丟擲Concurrent Modification Exception。

4. fail-safe機制

fail-safe任何對集合結構的修改都會在一個複製的集合上進行修改,因此不會丟擲ConcurrentModificationException
fail-safe機制有兩個問題
(1)需要複製集合,產生大量的無效物件,開銷大
(2)無法保證讀取的資料是目前原始資料結構中的資料。

一:快速失敗(fail—fast)
在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了修改(增加、刪除、修改),則會丟擲Concurrent Modification Exception。

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

注意:這裡異常的丟擲條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設定為了expectedmodCount值,則異常不會丟擲。因此,不能依賴於這個異常是否丟擲而進行併發操作的程式設計,這個異常只建議用於檢測併發修改的bug。

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

二:安全失敗(fail—safe)

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

原理:由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception。

缺點:基於拷貝內容的優點是避免了Concurrent Modification Exception,但同樣地,迭代器並不能訪問到修改後的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。

場景:java.util.concurrent包下的容器都是安全失敗,可以在多執行緒下併發使用,併發修改。
在這裡插入圖片描述