面試題思考:java中快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什麽?
一:快速失敗(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包下的容器都是安全失敗,可以在多線程下並發使用,並發修改。
另一篇博客:
在我們詳細討論這兩種機制的區別之前,首先得先了解並發修改。
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 |
面試題思考:java中快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什麽?