Почему возникает ConcurrentModificationException
постоянно получаю ошибку, когда пытаюсь добавить объекты на карту. Гугл мне не дал ясности, стек оверфлоу я тоже, не понял, что пытается донести. Единственное, что понял, одновременно читаю и записываю в этот список. p.s. прошу прощения за орфографию
private void addGeoObjectsToMap() < Listlist = getCurrentPageObjectsList(); if (list != null) < for (GeoObject obj : list) < addUserMarker(obj); >> >
Отслеживать
23.3k 1 1 золотой знак 18 18 серебряных знаков 29 29 бронзовых знаков
задан 2 мар 2015 в 7:08
1,890 2 2 золотых знака 19 19 серебряных знаков 31 31 бронзовый знак
javadoc про CurrentModificationException и про списки читать пробовали?
2 мар 2015 в 7:36
@a_gura, да читал, но как правильно реализовать, я так и не понял
2 мар 2015 в 9:11
@metalurgus беда в том, что я его именно скопировал 🙂
2 мар 2015 в 9:14
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
ConcurrentModificationException выбрасывается тогда, когда вы пытаетесь вставлять или удалять из коллекции элементы во время перебора элементов этой коллекции. Это потенциально может привести к некорректному поведению итератора (возвращению неверного следующего элемента или некорректному результату метода hasNext() ), поэтому все стандартные коллекции предпочитают сразу пресечь подобное поведение.
Если вам необходимо удалять элементы из коллекции во время перебора её элементов, то вам следует использовать явный перебор при помощи итератора:
List strList = new ArrayList<>(); Iterator strIter = strList.iteraror(); while (strIter.hasNext()) < String str = strIter.next(); if (/* condition */) < strIter.remove(); >>
Вставка элментов в коллекцию во время перебора её элементов стандартными коллекциями не поддерживается.
UPD
дело в том, что мне не нужно одновременно читать и записывать в коллекцию
Будем считать, что не — это опечатка и вам на самом деле нужно это делать. В таких случаях делают копию исходной коллекции. Итерация идёт по исходной коллекции, а вставка и удаление — из копии. После завершения цикла исходную коллекцию заменяют на новую.
Избавляемся от ConcurrentModificationException
Как известно, ConcurrentModificationException к многопоточности никакого отношения не имеет. Возникает эта гадость, когда мы пытаемся модифицировать коллекцию во время итерирования по ней. Как обычно, это имеет исторические корни: коллекции и итераторы появились в Java 1.2, в те времена избежать явного использования итератора при обходе коллекции было никак нельзя, так что предложение менять коллекцию посредством методов итератора не выглядело совсем ужасным:
Iterator iterator = collection.iterator(); while (iterator.hasNext()) < Object element = iterator.next(); if (iDontLikeThisElement(element)) < iterator.remove(); >>
Не, всё же выглядело. Но никаких других вариантов не было. Позже в пятой джаве появляется цикл foreach, и использование итераторов становится преимущественно неявным:
for (E element : collection) < if (iDonLikeThisElement(element)) < collection.remove(element); // облом! ConcurrentModificationException! >>
«Ишь чего захотели! Юзайте явные итераторы, дорогие кастомеры, и не выделывайтесь» — наверное что-то такое думали разработчики джава платформы работая над пятеркой.
В шестой джаве появляется пакет конкаренси. Теперь можно cделать так:
Set set = Collections.newSetFromMap(new ConcurrentHashMap<>());
И получить set который не кидается ConcurrentModificationException-ами. Но опять же счастье не совсем полное:
- Oбычно многопоточность нам вовсе не нужна
- Не подерживаются null ни в качестве элементов, ни ключей, ни значений. Да и ладно, честно сказать.
- Порядок элементов не определён и может меняться — вот это гораздо хуже. Т.е. если мы бежим по элементам и ведём некий подсчёт с потерей точности, то нас могут поджидать неприятные сюрпризы и разные результаты на одних и тех же наборах данных, что, скажем, не всегда хорошо. Так же бывают задачи, где желательно сохранить именно изначальный порядок данных. Ну и вот такие штуки тоже имеют место быть:
set.add("aaa"); set.add("bbb"); for (String s : set)
aaa
bbb
ddd
set.add("aaa"); set.add("bbb"); for (String s : set)
- В рамках одного треда можно добавлять и удалять элементы в любой момент без всяких эксепшенов. И конечно же за константное время.
- Можно хранить null-ы, если вдруг хочется.
- Элементы обходятся в том порядке в котором были добавлены.
- Удаляя элемент мы не будем обнулять ссылку на следующий, т. е. eсли итератор стоит на данном элементе, то он сможет пройти дальше.
- В конце списка поместим фэйковый элемент, который превращается в настоящий когда в список что-нибудь добавляют. Т.е. даже добравшись до конца списка итератор не упирается в null и может продолжить работу если в коллекции появляется новый элемент. Далее в коде этот фейковый элемент называется placeholder.

- В начале у нас есть элементы A, B, C, D.
- Затем элементы C и D удаляются.
- Добавляется новый элемент E.
Ну и для константного времени доступа нам, очевидно, нужен хэшмап:
import java.util.AbstractSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; public class LinkedSet extends AbstractSet < private static class LinkedElement < E value; boolean exists; LinkedElementprev; LinkedElement next; > private Map map = new HashMap<>(); private LinkedElement placeholder = new LinkedElement<>(); private LinkedElement head = placeholder; @Override public boolean isEmpty() < return head == placeholder; >@Override public int size() < return map.size(); >@Override public boolean contains(Object o) < return map.containsKey(o); >// здесь будут методы для добавления, удаления, итерирования >
@Override public boolean add(E e) < LinkedElementelement = map.putIfAbsent(e, placeholder); if (element != null) < return false; >element = placeholder; element.exists = true; element.value = e; placeholder = new LinkedElement<>(); placeholder.prev = element; element.next = placeholder; return true; >
@Override public boolean remove(Object o) < LinkedElementremovedElement = map.remove(o); if (removedElement == null) < return false; >removeElementFromLinkedList(removedElement); return true; > private void removeElementFromLinkedList(LinkedElement element) < element.exists = false; element.value = null; element.next.prev = element.prev; if (element.prev != null) < element.prev.next = element.next; element.prev = null; >else < head = element.next; >>
@Override public Iterator iterator() < return new ElementIterator(); >private class ElementIterator implements Iterator < LinkedElementnext = head; LinkedElement current = null; LinkedElement findNext() < LinkedElementn = next; while (!n.exists && n.next != null) < next = n = n.next; >return n; > @Override public boolean hasNext() < return findNext().exists; >@Override public E next() < LinkedElementn = findNext(); if (!n.exists) < throw new NoSuchElementException(); >current = n; next = n.next; return n.value; > @Override public void remove() < if (current == null) < throw new IllegalStateException(); >if (map.remove(current.value, current)) < removeElementFromLinkedList(current); >else < throw new NoSuchElementException(); >> >
Теперь можно делать так:
Set set = new LinkedSet<>(); // . put some numbers set.stream().filter(v -> v % 2 == 0).forEach(set::remove);
Понятно, что аналогично можно сконструировать и LinkedMap. Вот в общем-то и всё, ещё один велосипед готов. Почему подобным образом не доработали библиотечные LinkedHashMap и LinkedHashSet? Кто знает, возможно чтобы джависты завидовали джаваскриптистам.
- java
- коллекции
- ConcurrentModificationException
Избегаем ConcurrentModificationException при удалении элементов из ArrayList во время итерации
Одной из распространенных проблем, с которыми сталкиваются разработчики на Java, является ConcurrentModificationException. Это исключение возникает, когда коллекция, по которой происходит итерация, изменяется в процессе этой итерации. Проблема становится особенно актуальной, когда нужно удалить элемент из списка во время итерации.
Пример кода, который вызывает это исключение:
ArrayList<String> list = new ArrayList<String>(); list.add("Java"); list.add("PHP"); list.add("Python"); for (String language : list) < if (language.equals("PHP")) < list.remove(language); >>
В этом примере попытка удалить элемент списка во время итерации через него вызывает ConcurrentModificationException.
Чтобы избежать этого исключения, можно использовать итератор и метод remove(). Итератор предоставляет безопасный способ удаления элементов из коллекции во время итерации.
Пример кода, который безопасно удаляет элемент из списка во время итерации:
ArrayList<String> list = new ArrayList<String>(); list.add("Java"); list.add("PHP"); list.add("Python"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) < String language = iterator.next(); if (language.equals("PHP")) < iterator.remove(); >>
Таким образом, для безопасного удаления элементов из коллекции во время итерации по ней следует использовать итератор и его метод remove().
Как избежать ConcurrentModificationException при пользовании коллекциями
Название ConcurrentModificationException многих вводит в заблуждение. При слове Concurrent первое что приходит на ум — многотредность. Однако, данное исключение относится вовсе не к многотредности. Исключение может происходить при работе с коллекциями при обычной однотредной работе.
Для «прохода» по коллекции используются структуры данных, реализующие паттерн «View». Структуры данных эти называются итераторами и могут использоваться явно и не явно. Неявно итераторы используются при использовании конструкции foreach.
ConcurrentModificationException возникает когда коллекция модифицируется «одновременно» с проходом по коллекции итератором любыми средствами кроме самого итератора.
Например, ниже при удалении элемента из map произойдет ConcurrentModificationException, поскольку в цикле for неявно формируется итератор, и из map удаляется элемент, в процессе прохода по map.
@Test(expected = ConcurrentModificationException.class) public void testForEachFail() < Mapmap = new HashMap(); map.put("a", "a"); map.put("b", "b"); for(String key:map.keySet()) < map.remove(key); >>
Заблуждения добавляет тот факт, что в случае, если в map в приведенном примере будет лишь один элемент, то ConcurrentModificationException не возникнет (ниже тот же код, но добавляется лишь один элемент в map):
@Test public void testForEachOneElement() < Mapmap = new HashMap(); map.put("a", "a"); for(String key:map.keySet()) < map.remove(key); >>
Тест выше проходит, не смотря на то, что не верен… Еще один пример неверного кода, уже со StackOverflow:
// @see http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap Iterator it = map.entrySet().iterator(); while (it.hasNext())
В вышеприведенном примере, СoncurrentModificationException возникает при явном использовании итератора для прохода по элементам. Но в то же самое время элемент удаляется по ключу: map.remove(item.getKey()).
Как же сделать удаление правильно? Правильно, нужно использовать метод итератора, для удаления элемента:
// @see http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap Iterator it = map.entrySet().iterator(); while (it.hasNext())
Краткое summary
ConcurrentModificationException не всегда является следствием неверной синхронизации работы с коллекциями. ConcurrentModificationException возникает при изменении коллекции любыми средствами, отличными от итератора, при проходе по коллекции с помощью итератора. Изменение такое, как продемонстрировано выше может происходить и в одно-тредной среде. Не лишне также добавить, что исключение может возникать не только при удалении, но и при добавлении элемента, а также что модификация контейнера может возникать и в многотредной среде. Так что исключение не обязательно тривиальное. Однако, прежде чем пугаться, есть повод проверить а не является ли это одним из приведенных выше антипаттернов.
Надежность библиотек Java усыпляет. Описанные выше примеры показывают, что в Java все таки есть способы «стрельнуть себе в ногу».