Автор оригинала: 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 в многопоточной среде
- Вы можете преобразовать список в массив, а затем выполнить итерацию по массиву. Этот подход хорошо работает для списка малого или среднего размера, но если список большой, это сильно повлияет на производительность.
- Вы можете заблокировать список во время итерации, поместив его в синхронизированный блок. Такой подход не рекомендуется, поскольку он лишит вас преимуществ многопоточности.
- Если вы используете 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
Из приведенного выше примера ясно, что:
- Классы параллельной коллекции можно безопасно изменять, они не будут вызывать исключение ConcurrentModificationException.
- В случае CopyOnWriteArrayList итератор не учитывает изменения в списке и работает с исходным списком.
- В случае 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, если это не так.