一:快速失敗(fail—fast)java
在用迭代器遍歷一個集合對象時,若是遍歷過程當中對集合對象的內容進行了修改(增長、刪除、修改),則會拋出Concurrent Modification Exception。編程
原理:迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個 modCount 變量。集合在被遍歷期間若是內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素以前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;不然拋出異常,終止遍歷。安全
注意:這裏異常的拋出條件是檢測到 modCount!=expectedmodCount 這個條件。若是集合發生變化時修改modCount值恰好又設置爲了expectedmodCount值,則異常不會拋出。所以,不能依賴於這個異常是否拋出而進行併發操做的編程,這個異常只建議用於檢測併發修改的bug。數據結構
場景:java.util包下的集合類都是快速失敗的,不能在多線程下發生併發修改(迭代過程當中被修改)。多線程
二:安全失敗(fail—safe)併發
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷。this
原理:因爲迭代時是對原集合的拷貝進行遍歷,因此在遍歷過程當中對原集合所做的修改並不能被迭代器檢測到,因此不會觸發Concurrent Modification Exception。spa
缺點:基於拷貝內容的優勢是避免了Concurrent Modification Exception,但一樣地,迭代器並不能訪問到修改後的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。線程
場景:java.util.concurrent包下的容器都是安全失敗,能夠在多線程下併發使用,併發修改。code
另外一篇博客:
在咱們詳細討論這兩種機制的區別以前,首先得先了解併發修改。
1.什麼是同步修改?
當一個或多個線程正在遍歷一個集合Collection,此時另外一個線程修改了這個集合的內容(添加,刪除或者修改)。這就是併發修改
2.什麼是 fail-fast 機制?
fail-fast機制在遍歷一個集合時,當集合結構被修改,會拋出Concurrent Modification Exception。
fail-fast會在如下兩種狀況下拋出ConcurrentModificationException
(1)單線程環境
集合被建立後,在遍歷它的過程當中修改告終構。
注意 remove()方法會讓expectModcount和modcount 相等,因此是不會拋出這個異常。
(2)多線程環境
當一個線程在遍歷這個集合,而另外一個線程對這個集合的結構進行了修改。
注意,迭代器的快速失敗行爲沒法獲得保證,由於通常來講,不可能對是否出現不一樣步併發修改作出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。所以,爲提升這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的作法:迭代器的快速失敗行爲應該僅用於檢測 bug。
3. fail-fast機制是如何檢測的?
迭代器在遍歷過程當中是直接訪問內部數據的,所以內部的數據在遍歷的過程當中沒法被修改。爲了保證不被修改,迭代器內部維護了一個標記 「mode」 ,當集合結構改變(添加刪除或者修改),標記"mode"會被修改,而迭代器每次的hasNext()和next()方法都會檢查該"mode"是否被改變,當檢測到被修改時,拋出Concurrent Modification Exception
下面看看ArrayList迭代器部分的源碼
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = ArrayList.this.modCount; public boolean hasNext() { return (this.cursor != ArrayList.this.size); } public E next() { checkForComodification(); /** 省略此處代碼 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此處代碼 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
能夠看到它的標記「mode」爲 expectedModeCount
4. fail-safe機制
fail-safe任何對集合結構的修改都會在一個複製的集合上進行修改,所以不會拋出ConcurrentModificationException
fail-safe機制有兩個問題
(1)須要複製集合,產生大量的無效對象,開銷大
(2)沒法保證讀取的數據是目前原始數據結構中的數據。
5 fail-fast 和 fail-safe的例子
import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class FailFastExample { public static void main(String[] args) { Map<String,String> premiumPhone = new HashMap<String,String>(); premiumPhone.put("Apple", "iPhone"); premiumPhone.put("HTC", "HTC one"); premiumPhone.put("Samsung","S5"); Iterator iterator = premiumPhone.keySet().iterator(); while (iterator.hasNext()) { System.out.println(premiumPhone.get(iterator.next())); premiumPhone.put("Sony", "Xperia Z"); } } }
輸出 iPhone Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(Unknown Source) at java.util.HashMap$KeyIterator.next(Unknown Source) at FailFastExample.main(FailFastExample.java:20)
import java.util.concurrent.ConcurrentHashMap; import java.util.Iterator; public class FailSafeExample { public static void main(String[] args) { ConcurrentHashMap<String,String> premiumPhone = new ConcurrentHashMap<String,String>(); premiumPhone.put("Apple", "iPhone"); premiumPhone.put("HTC", "HTC one"); premiumPhone.put("Samsung","S5"); Iterator iterator = premiumPhone.keySet().iterator(); while (iterator.hasNext()) { System.out.println(premiumPhone.get(iterator.next())); premiumPhone.put("Sony", "Xperia Z"); } } }
輸出
S5 HTC one iPhone
6. fail-fast和 fail-safe 的區別
Fail Fast Iterator | Fail Safe Iterator | |
---|---|---|
Throw ConcurrentModification Exception | Yes | No |
Clone object | No | Yes |
Memory Overhead | No | Yes |
Examples | HashMap,Vector,ArrayList,HashSet | CopyOnWriteArrayList,ConcurrentHashMap |