Модификация коллекции Java в однопоточной и многопоточной среде

FAANG Master
2 min readJun 20, 2023

--

Что будет результатом выполнения такой программы?

public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
for (String el : list) {
if (el.equals("b")) {
list.remove(el);
}
}
System.out.println(list);
}
}

Эта программа бросит исключение ConcurrentModificationException. Почему так происходит? for-each цикл в Java использует Iterator. И если мы используем итератор, то удаление нужно делать при помощи итератора. Если же мы удаление делаем при помощи метода list.remove(el), то при вызове метода next будет брошено исключение ConcurrentModificationException:

Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)

Как это исправить?

Можно использовать Iterator явно и вызвать метод remove итератора:

public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String el = (String) iterator.next();
if (el.equals("b")) {
iterator.remove();
}
}
System.out.println(list);
}
}

Результат:

[a, c, d]

Или же можно использовать removeIf:

public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.removeIf(el -> el.equals("b"));
System.out.println(list);
}
}

Или не использовать итератор вообще:

public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
for (int i = 0; i < list.size(); i++) {
String el = list.get(i);
if (el.equals("b")) {
list.remove(el);
}
}
System.out.println(list);
}
}

Многопоточная среда:

Допустим у нас в одном потоке есть итерирование по коллекции, а во втором потоке у нас происходит модификация этой коллекции (удаление или добавление элемента). Чтобы предотвратить ConcurrentModificationException можно использовать:

  1. synchronized блок перед итерированием:
  2. Использовать потокобезопасные коллекции: ConcurrentHashMap, CopyOnWriteArrayList

Посмотрим вариант с synchonized блоком:

Первый поток:
synchronized (list) {
for (String el : list) {
System.out.println(el);
}
}
Второй поток:
synchronized (list) {
list.remove("b");
}

Можно ли избежать synchronized блока, если использовать:

Collections.synchronizedList(list);

Ответ: нет. В таком случае может быть также брошен ConcurrentModificationException. В таком случае также нужно помещать в synchonized блок.

Если мы используем ConcurrentHashMap, CopyOnWriteArrayList то можно итерироваться по коллекции в одном потоке и модифицировать ее в другом без synchonized блока не опасаясь ConcurrentModificationException.

--

--

FAANG Master
FAANG Master

Written by FAANG Master

Статьи теперь пишу на https://dev.to/faangmaster, Мой телеграм канал: https://t.me/faangmaster, Мой youtube: https://www.youtube.com/@FAANGMaster

No responses yet