快速失敗(fail-fast)和安全失敗(fail-safe)
在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了修改(增加、刪除、修改),則會丟擲ConcurrentModificationException。
-
原理:迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個modCount變數。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hasNext()/next()遍歷下一個元素之前,都會檢測modCount變數是否為expectedModCount值,是的話就返回遍歷;否則丟擲異常,終止遍歷。
-
注意:這裡異常的丟擲條件是檢測到modCount != expectedModCount這個條件。如果集合發生變化時修改modCount值剛好又設定為了expectedModCount值,則異常不會丟擲。因此,不能依賴於這個異常是否丟擲而進行併發操作的變成,這個異常只建議用於檢測併發修改的bug。
- 場景:java.utl包下的集合類都是快速失敗的,不能再多執行緒下發生併發修改(迭代過程中被修改)。
安全失敗(fail-safe)
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問,而是先複製原有集合內容,在拷貝的集合上進行遍歷。
- 原理:由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發ConcurrentModificationException。
-
缺點:
- 基於拷貝內容的優點是避免了ConcurrentModificationException,但同樣地,迭代器並不能訪問到修改後的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。
- 需要複製集合,產生大量的無效物件,開銷大。
- 場景:java.util.concurrent包下的容器都是安全失敗,可以在多執行緒下併發使用,併發修改。
快速失敗和安全失敗是對迭代器而言的。快速失敗:擋在迭代一個集合的時候,如果有另外一個執行緒在修改這個集合,就會丟擲ConcurrentModificationException,java.utl下都是快速失敗。安全失敗:在迭代時候會在集合二層做一個拷貝,所以在修改集合上層元素不會影響下層。在java.util.concurrent下都是安全的。