Java中的fail-fast機制
遍歷刪除List中的元素有很多種方法,當運用不當的時候就會產生問題。下面主要看看以下幾種遍歷刪除List中元素的形式:
1.通過普通的for刪除刪除符合條件的一個元素
2.通過增強的for循環刪除符合條件的一個元素
3.通過增強的for循環刪除符合條件的一個元素並立即跳出
4.通過Iterator進行遍歷刪除符合條件的一個元素
1 public class listFailFast { 2 3 /** 4 * 初始化list 5 * @return 6 */ 7 public List<String> getListStr(){8 List<String> list = new ArrayList<String>(); 9 list.add("test 1"); 10 list.add("test 2"); 11 list.add("test 3"); 12 list.add("test 4"); 13 return list; 14 } 15 16 /** 17 * 這種不使用增強的for循環的也可以正常刪除和遍歷,18 * 這裏所謂的正常是指它不會報異常,但是刪除後得到的 19 * 數據不一定是正確的,這主要是因為刪除元素後,被刪除元素後 20 * 的元素索引發生了變化。假設被遍歷list中共有10個元素,當 21 * 刪除了第3個元素後,第4個元素就變成了第3個元素了,第5個就變成 22 * 了第4個了,但是程序下一步循環到的索引是第4個, 23 * 這時候取到的就是原本的第5個元素了。 24 */ 25 @Test 26 public void listRemoveByFor(){27 List<String> list = this.getListStr(); 28 for(int i=0; i<list.size(); i++){ 29 String str = list.get(i); 30 if(list.get(1).equals(str)){ 31 list.remove(str); 32 } 33 System.out.println(str); 34 } 35 } 36 37 /** 38 * 使用增強的for循環 39 * 在循環過程中從List中刪除非基本數據類型以後,繼續循環List時會報ConcurrentModificationException 40 */ 41 @Test 42 public void listRemoveByForeach(){ 43 List<String> list = this.getListStr(); 44 for(String str : list){ 45 if(list.get(1).equals(str)){ 46 list.add(str); 47 } 48 System.out.println(str); 49 } 50 } 51 52 /** 53 * 使用增強的for循環 54 * 在循環過程中從List中刪除非基本數據類型以後,立即跳出,不會出現異常 55 */ 56 @Test 57 public void listRemoveBreakByForeach(){ 58 List<String> list = this.getListStr(); 59 for(String str : list){ 60 if(list.get(1).equals(str)){ 61 list.add(str); 62 break; 63 } 64 System.out.println(str); 65 } 66 } 67 68 /** 69 * 使用Iterator的方式可以順利刪除和遍歷 70 */ 71 @Test 72 public void iteratorRemove() { 73 List<String> list = this.getListStr(); 74 System.out.println(list); 75 Iterator<String> strIter = list.iterator(); 76 while (strIter.hasNext()) { 77 String str = strIter.next(); 78 if (str.contains("2")){ 79 strIter.remove();//這裏要使用Iterator的remove方法移除當前對象,如果使用List的remove方法,則同樣會出現ConcurrentModificationException 80 } 81 System.out.println(str); 82 } 83 System.out.println(list); 84 } 85 86 /** 87 * 使用Iterator的方式可以順利刪除和遍歷 88 */ 89 @Test 90 public void iteratorListRemove() { 91 List<String> list = this.getListStr(); 92 System.out.println(list); 93 Iterator<String> strIter = list.iterator(); 94 while (strIter.hasNext()) { 95 String str = strIter.next(); 96 if (str.contains("2")){ 97 list.remove(1);//使用List的remove方法,同樣會出現ConcurrentModificationException, 98 // strIter.remove();//這裏要使用Iterator的remove方法移除當前對象 99 } 100 System.out.println(str); 101 } 102 System.out.println(list); 103 } 104 }
那麽看到以上的幾段代碼之後,我們來分析一下他的原因,分析原因之前我們先來認識一個詞fail-fast
fail-fast 機制是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。
例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那麽線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。
要了解fail-fast機制,我們首先要對ConcurrentModificationException 異常有所了解。當方法檢測到對象的並發修改,但不允許這種修改時就拋出該異常。同時需要註意的是,該異常不會始終指出對象已經由不同線程並發修改,如果單線程違反了規則,同樣也有可能會拋出改異常。
誠然,叠代器的快速失敗行為無法得到保證,它不能保證一定會出現該錯誤,但是快速失敗操作會盡最大努力拋出ConcurrentModificationException異常,所以因此,為提高此類操作的正確性而編寫一個依賴於此異常的程序是錯誤的做法,正確做法是:ConcurrentModificationException 應該僅用於檢測 bug。
當使用 fail-fast iterator
對 Collection
或 Map進行叠代操作過程中嘗試直接修改 Collection / Map
的內容時,即使是在單線程下運行,java.util.ConcurrentModificationException
異常也將被拋出。
Iterator是工作在一個獨立的線程中,並且擁有一個 mutex 鎖。 Iterator被創建之後會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指針往後移動的時候就找不到要叠代的對象,所以按照 fail-fast 原則 Iterator 會馬上拋出java.util.ConcurrentModificationException
異常。
所以 Iterator 在工作的時候是不允許被叠代的對象被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除對象, Iterator.remove() 方法會在刪除當前叠代對象的同時維護索引的一致性。
有意思的是如果你的 Collection / Map 對象實際只有一個元素的時候,ConcurrentModificationException
異常並不會被拋出。這也就是為什麽在 javadoc 裏面指出: itwould be wrong to write a program that depended on this exception forits correctness: ConcurrentModificationException should be used only todetect bugs
.
附:來自ibm developerworks上對java.util.concurrent包的說明片段: java.util 包中的集合類都返回 fail-fast 叠代器,這意味著它們假設線程在集合內容中進行叠代時,集合不會更改它的內容。如果fail-fast 叠代器檢測到在叠代過程中進行了更改操作,那麽它會拋出 ConcurrentModificationException,這是不可控異常。 在叠代過程中不更改集合的要求通常會對許多並發應用程序造成不便。相反,比較好的是它允許並發修改並確保叠代器只要進行合理操作,就可以提供集合的一致視圖,如 java.util.concurrent 集合類中的叠代器所做的那樣。 java.util.concurrent 集合返回的叠代器稱為弱一致的(weakly consistent)叠代器。對於這些類,如果元素自從叠代開始已經刪除,且尚未由 next()方法返回,那麽它將不返回到調用者。如果元素自叠代開始已經添加,那麽它可能返回調用者,也可能不返回。在一次叠代中,無論如何更改底層集合,元素不會被返回兩次。
原文鏈接:Java中的fail-fast機制
Java中的fail-fast機制