如何避免ConcurrentModificationException

weixin_43664019發表於2020-12-24

ConcurrentModificationException出現原因:

1.在Java集合類中,當我們在使用iterator遍歷集合物件時,當集合被改變且在使用迭代器遍歷集合的時候,迭代器的next()方法將丟擲ConcurrentModificationException異常。
2.如果subList()生成一個新的List物件,那麼當修改原始List物件的結構時,則將丟擲ConcurrentModificationException

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ConcurrentModificationExceptionExample {

	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")) {
			//由於集合被改變,所以接下來的it.next()會丟擲異常
				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中的現有鍵值,因此其大小沒有更改,所以沒有獲取ConcurrentModificationException
			//通過檢視原始碼,HashMap的next方法是呼叫nextNode().key;獲得的,而nextNode()判斷修改的標誌的是modCount != expectedModCount,即數量不變就為意味著沒有修改
			//modCount 的定義:The number of times this HashMap has been structurally modified
				myMap.put("1", "4");
				//如果放開註釋,接下來的 it1.next()就會丟擲異常
				// myMap.put("4", "4");
			}
		}

	}
}

結果:

List Value:1
List Value:2
List Value:3
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 901 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList Itr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.next(ArrayList.java:851)
at ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:20)

如何避免這個異常呢?

1.在單執行緒下,所以iterator的remove()方法從原集合中刪除一個元素

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ConcurrentModificationExceptionExample {

	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")) {
				it.remove();
			}

		}
		System.out.println("List Value:" + myList);
		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");
				it1.remove();
			}
		}
		System.out.println("myMap:" + myMap);

	}
}

執行結果:
List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Value:[1, 2, 4, 5]
Map Value:1
Map Value:2
Map Value:3
myMap:{1=4, 3=3}

在多執行緒環境中避免ConcurrentModificationException

1.您可以將列表轉換為陣列,然後在陣列上進行迭代。這種方法適用於中小型列表,但是如果列表很大,則對效能的影響很大。
2.如果使用的是JDK1.5或更高版本,則可以使用ConcurrentHashMap和CopyOnWriteArrayList類。建議使用此方法來避免併發修改異常。



import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class AvoidConcurrentModificationException {

	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");
				myList.remove("3");
			}
		}
		System.out.println("List Size:" + myList);

		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);
	}

}


結果:
List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:[1, 2, 5, 6, 7]
Map Value:1
Map Value:2
Map Value:4
Map Value:5
Map Size:{1=1, 2=2, 4=4, 5=5}

為什麼CopyOnWriteArrayList使用iterator時修改原資料就不會報錯呢?

   public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

CopyOnWriteArrayList內部靜態類
  static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;
//傳入的是物件,所以原資料修改並不影響它
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        public boolean hasPrevious() {
            return cursor > 0;
        }
        ..........

如果subList()生成一個新的List物件,那麼當修改原始List物件的結構時,則將丟擲ConcurrentModificationException


import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExceptionWithArrayListSubList {

	public static void main(String[] args) {

		List<String> names = new ArrayList<>();
		names.add("Java");
		names.add("PHP");
		names.add("SQL");
		names.add("Angular 2");

		List<String> first2Names = names.subList(0, 2);

		System.out.println(names + " , " + first2Names);

//		names.set(1, "JavaScript");
		// names.add("Oracle");
		// check the output below. :)
		System.out.println(names);
		System.out.println(first2Names);

		// Let's modify the list size and get ConcurrentModificationException
		// names.add("NodeJS");
		System.out.println(names); // this line throws exception
		first2Names.set(1, "RabbitMQ");
//		System.out.println(first2Names);
		System.out.println(names);
		System.out.println(first2Names);

		names.add(1, "RabbitMQ2");
		//下一行出現異常
		first2Names.set(1, "RabbitMQ3");
	}

}

結果:
[Java, PHP, SQL, Angular 2] , [Java, PHP]
[Java, PHP, SQL, Angular 2]
[Java, PHP]
[Java, PHP, SQL, Angular 2]
[Java, RabbitMQ, SQL, Angular 2]
[Java, RabbitMQ]
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayList S u b L i s t . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 1231 ) a t j a v a . u t i l . A r r a y L i s t SubList.checkForComodification(ArrayList.java:1231) at java.util.ArrayList SubList.checkForComodification(ArrayList.java:1231)atjava.util.ArrayListSubList.set(ArrayList.java:1027)
at ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:34)

參考文章
CopyOnWriteArrayList

java.util.ConcurrentModificationException

相關文章