Рубрики
Без рубрики

Ссылка: || API Doc

java.util.Исключение ConcurrentModificationException. Избегайте исключения одновременной модификации с использованием параллельных классов в одно-и многопоточных программах.

Автор оригинала: Pankaj Kumar.

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

Исключение одновременной модификации может возникнуть в случае многопоточной, а также однопоточной среды программирования Java.

Исключение одновременной модификации может возникнуть в случае многопоточной, а также однопоточной среды программирования Java.

Давайте рассмотрим сценарий исключения одновременного изменения на примере.

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 myList = new ArrayList();

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator it = myList.iterator();
		while (it.hasNext()) {
			String value = it.next();
			System.out.println("List Value:" + value);
			if (value.equals("3"))
				myList.remove(value);
		}

		Map myMap = new HashMap();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator 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)

Из трассировки выходного стека ясно, что исключение параллельной модификации возникает при вызове функции iterator next () .

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

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

Выход:

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

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

Если вы раскомментируете инструкцию, в которой я добавляю новое значение ключа в хэш-карту, это вызовет исключение ConcurrentModificationException.

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

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

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

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

Давайте рассмотрим пример с использованием параллельных классов коллекций.

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 myList = new CopyOnWriteArrayList();

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator 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 myMap = new ConcurrentHashMap();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator 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. Классы параллельной коллекции можно безопасно изменять, они не будут вызывать исключение ConcurrentModificationException.
  2. В случае CopyOnWriteArrayList итератор не учитывает изменения в списке и работает с исходным списком.
  3. В случае ConcurrentHashMap поведение не всегда одно и то же.Для состояния:

    Выход есть:

    Он принимает новый объект, добавленный с ключом “4”, но не следующий добавленный объект с ключом “5”.

    Теперь, если я изменю условие на приведенное ниже.

    Выход есть:

    В данном случае это не учитывает вновь добавленные объекты.

    Поэтому, если вы используете ConcurrentHashMap, избегайте добавления новых объектов, так как они могут обрабатываться в зависимости от набора ключей. Обратите внимание, что одна и та же программа может печатать разные значения в вашей системе, поскольку набор ключей HashMap не упорядочен.

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

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

for(int i = 0; i

Обратите внимание, что я уменьшаю счетчик, потому что удаляю один и тот же объект, если вам нужно удалить следующий или более удаленный объект, вам не нужно уменьшать счетчик. Попробуйте сами. 🙂

Еще одна вещь : Вы получите исключение ConcurrentModificationException, если попытаетесь изменить структуру исходного списка с помощью подсписка. Давайте посмотрим на это на простом примере.

package com.journaldev.ConcurrentModificationException;

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

public class ConcurrentModificationExceptionWithArrayListSubList {

	public static void main(String[] args) {

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

		List 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, если это не так.