在多線程以及單線程環境下都可能出現這種情況。
讓我們通過以下示例探索這種情況:
import java.util.*;public class IteratorExample {public static void main(String args[]){List<String> myList = new ArrayList<String>();myList.add("1");myList.add("2");myList.add("3");myList.add("4");myList.add("5");Iterator<String> it = myList.iterator();while(it.hasNext()){String value = it.next();System.out.println("List Value:"+value);if(value.equals("3")) myList.remove(value);}Map<String,String> myMap = new HashMap<String,String>();myMap.put("1", "1");myMap.put("2", "2");myMap.put("3", "3");Iterator<String> it1 = myMap.keySet().iterator();while(it1.hasNext()){String key = it1.next();System.out.println("Map Value:"+myMap.get(key));if(key.equals("2")){myMap.put("1","4");//myMap.put("4", "4");}}}
}
輸出為:
List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)at java.util.AbstractList$Itr.next(AbstractList.java:343)at com.journaldev.java.IteratorExample.main(IteratorExample.java:27)
從輸出堆棧跟蹤中可以明顯看出,當我們調用迭代器next()函數時,異常即將到來。 如果您想知道Iterator如何檢查修改,則它的實現存在于AbstractList類中,其中定義了一個int變量modCount,該變量提供了更改列表大小的次數。 該值在每個next()調用中使用,以檢查功能checkForComodification()中是否有任何修改。
現在,注釋列表部分并再次運行程序。
輸出將是:
Map Value:3
Map Value:2
Map Value:4
由于我們正在更新myMap中的現有鍵值,因此其大小沒有更改,并且沒有收到ConcurrentModificationException。 請注意,輸出結果可能在您的系統中有所不同,因為HashMap鍵集的排序方式與列表不同。 如果您將在HashMap中添加新鍵值的語句取消注釋,則會導致ConcurrentModificationException。
要在多線程環境中避免ConcurrentModificationException:
1.您可以將列表轉換為數組,然后在數組上進行迭代。 這種方法適用于中小型列表,但是如果列表很大,則對性能的影響很大。
2.您可以通過將列表放在同步塊中來在鎖定時鎖定列表。 不建議使用此方法,因為它將停止多線程的好處。
3.如果您使用的是JDK1.5或更高版本,則可以使用ConcurrentHashMap和CopyOnWriteArrayList類。 這是推薦的方法。
要在單線程環境中避免ConcurrentModificationException:
您可以使用迭代器remove()函數從基礎集合對象中刪除該對象。 但是在這種情況下,您可以從列表中刪除同一對象,而不能刪除任何其他對象。
讓我們使用并發集合類運行示例:
package com.journaldev.java;import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;public class ThreadSafeIteratorExample {public static void main(String[] args) {List<String> myList = new CopyOnWriteArrayList<String>();myList.add("1");myList.add("2");myList.add("3");myList.add("4");myList.add("5");Iterator<String> it = myList.iterator();while(it.hasNext()){String value = it.next();System.out.println("List Value:"+value);if(value.equals("3")){myList.remove("4");myList.add("6");myList.add("7");}}System.out.println("List Size:"+myList.size());Map<String,String> myMap = new ConcurrentHashMap<String,String>();myMap.put("1", "1");myMap.put("2", "2");myMap.put("3", "3");Iterator<String> it1 = myMap.keySet().iterator();while(it1.hasNext()){String key = it1.next();System.out.println("Map Value:"+myMap.get(key));if(key.equals("1")){myMap.remove("3");myMap.put("4", "4");myMap.put("5", "5");}}System.out.println("Map Size:"+myMap.size());}}
輸出為:
List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:6
Map Value:1
Map Value:null
Map Value:4
Map Value:2
Map Size:4
從上面的示例可以清楚地看出:
1.可以修改Concurrent Collection類,避免ConcurrentModificationException 。
2.對于CopyOnWriteArrayList ,迭代器不適應列表中的更改,并且可以處理原始列表。
3.對于ConcurrentHashMap ,其行為并不總是相同的。
條件:
if(key.equals("1")){myMap.remove("3");
輸出為:
Map Value:1
Map Value:null
Map Value:4
Map Value:2
Map Size:4
它正在使用添加了鍵“ 4”的新對象。 但不是下一個添加的鍵為“ 5”的對象。
現在,如果我將條件更改為
if(key.equals("3")){myMap.remove("2");
輸出為:
Map Value:1
Map Value:3
Map Value:null
Map Size:4
在這種情況下,它不考慮新添加的對象。
因此,如果您使用的是ConcurrentHashMap,請避免添加新對象,因為可以根據鍵集對其進行處理。 請注意,同一程序可以在您的系統中打印不同的值,因為HashMap鍵集沒有任何順序。
額外的澆頭:
for(int i = 0; i<myList.size(); i++){System.out.println(myList.get(i));if(myList.get(i).equals("3")){myList.remove(i);i--;myList.add("6");}
}
如果您正在單線程環境中工作,并且希望您的代碼處理列表中額外添加的對象,則可以使用以下代碼并避免使用迭代器。
請注意,由于要刪除同一對象,所以要減少計數器,如果必須刪除下一個或更遠的對象,則不需要減少計數器。
自己嘗試。
參考:在JournalDev上 使用 JCG合作伙伴提供的迭代器時如何避免ConcurrentModificationException 。
相關文章:
- Java最佳實踐– Vector vs ArrayList vs HashSet
- Java最佳實踐–隊列之戰和鏈接的ConcurrentHashMap
- Java Fork / Join進行并行編程
- ConcurrentLinkedHashMap v 1.0.1發布
- 阻塞隊列示例以執行命令
- 限制URL連接的信號量示例
- 執行命令的同步隊列示例
- 更一般的等待/通知機制的CountDownLatch示例
翻譯自: https://www.javacodegeeks.com/2011/05/avoid-concurrentmodificationexception.html