Что будет результатом выполнения такой программы?
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 можно использовать:
synchronized блок перед итерированием:
Использовать потокобезопасные коллекции: 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.
Top comments (1)
Добрый день!
Очень тяжело дается чтение книги "Java Concurrency in Action".
Какую еще книгу могли бы посоветовать по этой теме?
P.s.: Спасибо за столь полезные посты!