Поиск по сайту:

java.util.ConcurrentModificationException


java.util.ConcurrentModificationException — очень распространенное исключение при работе с классами коллекций Java. Классы Java Collection являются отказоустойчивыми, что означает, что если Коллекция будет изменена, когда какой-либо поток проходит через нее с помощью итератора, iterator.next() вызовет исключение ConcurrentModificationException. Исключение параллельной модификации может возникнуть как в случае многопоточной, так и однопоточной среды программирования Java.

java.util.ConcurrentModificationException

package com.journaldev.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"))
				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");
			}
		}

	}
}

Приведенная выше программа будет вызывать java.util.ConcurrentModificationException при выполнении, как показано в приведенных ниже журналах консоли.

List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:22)

Из трассировки выходного стека видно, что исключение параллельной модификации возникает, когда мы вызываем функцию итератора next(). Если вам интересно, как Iterator проверяет модификацию, его реализация присутствует в классе AbstractList, где определена переменная типа modCount. modCount указывает, сколько раз размер списка был изменен. Значение modCount используется при каждом вызове next() для проверки любых изменений в функции checkForComodification(). Теперь закомментируйте часть списка и снова запустите программу. Вы увидите, что сейчас нет ConcurrentModificationException. Выход:

Map Value:3
Map Value:2
Map Value:4

Поскольку мы обновляем существующее значение ключа в myMap, его размер не изменился, и мы не получаем исключение ConcurrentModificationException. Вывод может отличаться в вашей системе, потому что набор ключей HashMap не упорядочен как список. Если вы раскомментируете оператор, в котором я добавляю новый ключ-значение в HashMap, это вызовет ConcurrentModificationException.

Чтобы избежать ConcurrentModificationException в многопоточной среде

  1. Вы можете преобразовать список в массив, а затем выполнить итерацию по массиву. Этот подход хорошо работает для списков небольшого или среднего размера, но если список большой, это сильно повлияет на производительность.
  2. Вы можете заблокировать список во время итерации, поместив его в синхронизированный блок. Этот подход не рекомендуется, так как он лишит вас преимуществ многопоточности.
  3. Если вы используете JDK1.5 или более позднюю версию, вы можете использовать классы ConcurrentHashMap и CopyOnWriteArrayList. Это рекомендуемый подход, чтобы избежать исключения одновременного изменения.

Чтобы избежать ConcurrentModificationException в однопоточной среде

Вы можете использовать функцию итератора remove(), чтобы удалить объект из базового объекта коллекции. Но в этом случае вы можете удалить этот же объект, а не любой другой объект из списка. Давайте запустим пример, используя классы Concurrent Collection.

package com.journaldev.ConcurrentModificationException;

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

}

Вывод вышеуказанной программы показан ниже. Вы можете видеть, что программа не генерирует исключение ConcurrentModificationException.

List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:6
Map Value:1
Map Value:2
Map Value:4
Map Value:5
Map Size:4

Из вышеприведенного примера видно, что:

  1. Concurrent Collection classes can be modified safely, they will not throw ConcurrentModificationException.

  2. In case of CopyOnWriteArrayList, iterator doesn’t accommodate the changes in the list and works on the original list.

  3. In case of ConcurrentHashMap, the behaviour is not always the same.For condition:

    if(key.equals("1")){
    	myMap.remove("3");}
    

    Output is:

    Map Value:1
    Map Value:null
    Map Value:4
    Map Value:2
    Map Size:4
    

    It is taking the new object added with key “4” but not the next added object with key “5”. Now if I change the condition to below.

    if(key.equals("3")){
    	myMap.remove("2");}
    

    Output is:

    Map Value:1
    Map Value:3
    Map Value:null
    Map Size:4
    

    In this case, it’s not considering the newly added objects. So if you are using ConcurrentHashMap then avoid adding new objects as it can be processed depending on the keyset. Note that the same program can print different values in your system because HashMap keyset is not ordered.

Используйте цикл for, чтобы избежать java.util.ConcurrentModificationException

Если вы работаете в однопоточной среде и хотите, чтобы ваш код позаботился о дополнительных объектах, добавленных в список, вы можете сделать это, используя цикл for, а не итератор.

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

Обратите внимание, что я уменьшаю счетчик, потому что я удаляю тот же объект, если вам нужно удалить следующий или более дальний объект, вам не нужно уменьшать счетчик. Попробуй сам. :) Еще одна вещь: вы получите исключение ConcurrentModificationException, если попытаетесь изменить структуру исходного списка с помощью subList. Давайте посмотрим на это на простом примере.

package com.journaldev.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");
		// check the output below. :)
		System.out.println(names + " , " + first2Names);

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

	}

}

Вывод вышеуказанной программы:

[Java, PHP, SQL, Angular 2] , [Java, PHP]
[Java, JavaScript, SQL, Angular 2] , [Java, JavaScript]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1282)
	at java.base/java.util.ArrayList$SubList.listIterator(ArrayList.java:1151)
	at java.base/java.util.AbstractList.listIterator(AbstractList.java:311)
	at java.base/java.util.ArrayList$SubList.iterator(ArrayList.java:1147)
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:465)
	at java.base/java.lang.String.valueOf(String.java:2801)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)
	at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:26)

Согласно документации подсписка ArrayList, структурные модификации разрешены только в списке, возвращаемом методом подсписка. Все методы в возвращенном списке сначала проверяют, равен ли фактический modCount резервного списка его ожидаемому значению, и выдают исключение ConcurrentModificationException, если это не так.

Вы можете загрузить весь пример кода из нашего репозитория GitHub.