Почему выбрасывается исключение ConcurrentModificationException и как его отлаживать - PullRequest
110 голосов
/ 02 марта 2009

Я использую Collection (HashMap, косвенно используемый JPA, так бывает), но, очевидно, случайным образом код выдает ConcurrentModificationException. Что вызывает это и как я могу решить эту проблему? Возможно, с помощью какой-то синхронизации?

Вот полная трассировка стека:

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)

Ответы [ 6 ]

240 голосов
/ 02 марта 2009

Это не проблема синхронизации. Это произойдет, если базовая коллекция, для которой выполняется итерация, модифицируется чем-либо, кроме самого итератора.

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

Это вызовет исключение ConcurrentModificationException при вызове it.hasNext () во второй раз.

Правильный подход будет

   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

Предполагая, что этот итератор поддерживает операцию удаления ().

56 голосов
/ 02 марта 2009

Попробуйте использовать ConcurrentHashMap вместо простого HashMap

8 голосов
/ 13 марта 2019

Модификация Collection при итерации по этому Collection с использованием Iterator не разрешена большинством Collection классов , Библиотека Java называет попытку изменить Collection, повторяя ее, «одновременной модификацией», которая, к сожалению, предполагает единственно возможную причину - одновременную модификацию несколькими потоками, но это не так. Используя только один поток, можно создать итератор для Collection (используя Collection.iterator() или расширенный for цикл ), начать итерацию (используя Iterator.next(), или эквивалентно вход в тело расширенного цикла for), измените Collection, затем продолжите итерацию.

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

Документация ConcurrentModificationException гласит:

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

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

Обратите внимание, что отказоустойчивое поведение не может быть гарантировано, поскольку вообще невозможно сделать какие-либо жесткие гарантии при наличии несинхронизированной параллельной модификации. Отказоустойчивые операции отбрасывают ConcurrentModificationException с максимальной отдачей.

Обратите внимание, что

В документации HashSet, HashMap, TreeSet и ArrayList говорится следующее:

Итераторы, возвращенные [прямо или косвенно из этого класса], не подвержены сбоям: если [коллекция] изменяется в любое время после создания итератора, любым способом, кроме как через собственный метод удаления итератора, Iterator бросает ConcurrentModificationException. Таким образом, перед одновременной модификацией итератор быстро и чисто дает сбой, вместо того, чтобы рисковать произвольным недетерминированным поведением в неопределенное время в будущем.

Обратите внимание, что отказоустойчивое поведение итератора не может быть гарантировано, поскольку вообще невозможно сделать какие-либо жесткие гарантии при наличии несинхронизированной одновременной модификации. Отказоустойчивые итераторы отбрасывают ConcurrentModificationException с максимальной отдачей. Следовательно, было бы неправильно писать программу, которая зависела от этого исключения в отношении его корректности: Поведение итераторов при быстром отказе следует использовать только для обнаружения ошибок .

Обратите внимание, что поведение «не может быть гарантировано» и действует только «на основе максимальных усилий».

Документация нескольких методов интерфейса Map гласит:

Неконкурентные реализации должны переопределить этот метод и, с максимальной отдачей, выбросить ConcurrentModificationException, если обнаружено, что функция отображения изменяет эту карту во время вычисления. Параллельные реализации должны переопределить этот метод и, с максимальной отдачей, выбросить IllegalStateException, если будет обнаружено, что функция отображения изменяет эту карту во время вычислений и в результате вычисление никогда не завершится.

Еще раз отметим, что для обнаружения требуется только «основа наилучшего возможного», а ConcurrentModificationException явно предлагается только для не параллельных (не ориентированных на многопотоковое исполнение) классов.

Отладка ConcurrentModificationException

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

  • Наиболее распространенной причиной является изменение Collection в расширенном цикле for над Collection. Тот факт, что вы не видите объект Iterator в исходном коде, не означает, что там нет Iterator! К счастью, один из операторов неисправного цикла for обычно находится в трассировке стека, поэтому отследить ошибку обычно легко.
  • Более сложный случай - когда ваш код передает ссылки на объект Collection. Обратите внимание, что неизменяемые представления коллекций (например, созданные Collections.unmodifiableList()) сохраняют ссылку на изменяемую коллекцию, поэтому итерация над «неизменяемой» коллекцией может вызвать исключение (модификация была сделана в другом месте). Другие представления ваших Collection, такие как подсписки , Map наборы записей и Map наборы ключей также сохраняют ссылки на оригинал (изменяемые) Collection. Это может быть проблемой даже для многопоточного Collection, такого как CopyOnWriteList; не думайте, что поточно-ориентированные (одновременные) сборы никогда не могут вызвать исключение.
  • Какие операции могут изменить Collection, может быть неожиданным в некоторых случаях. Например, LinkedHashMap.get() изменяет свою коллекцию .
  • Наиболее сложными являются случаи, когда исключение равно из-за одновременного изменения несколькими потоками.

Программирование для предотвращения одновременных ошибок модификации

Когда это возможно, ограничивайте все ссылки объектом Collection, чтобы было легче предотвратить одновременные изменения. Сделайте объект Collection a private или локальную переменную и не возвращайте ссылки на Collection или его итераторы из методов. Тогда намного проще исследовать все места, где Collection можно изменить. Если Collection будет использоваться несколькими потоками, тогда практично обеспечить, чтобы потоки обращались к Collection только с соответствующей синхронизацией и блокировкой.

2 голосов
/ 02 марта 2009

Это меньше похоже на проблему синхронизации Java и больше похоже на проблему блокировки базы данных.

Я не знаю, удастся ли отсортировать версию по всем вашим постоянным классам, но это один из способов, с помощью которого Hibernate может предоставить эксклюзивный доступ к строкам таблицы.

Может быть, уровень изоляции должен быть выше. Если вы разрешаете «грязное чтение», возможно, вам нужно перейти к сериализуемому.

0 голосов
/ 06 июля 2018

Обратите внимание, что выбранный ответ не может быть применен к вашему контексту непосредственно перед некоторой модификацией, если вы пытаетесь удалить некоторые записи с карты, повторяя карту, как я.

Я просто привожу свой рабочий пример, чтобы новички сэкономили свое время:

HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
    Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
    //it.remove() will delete the item from the map
    if((Integer)item.getValue()<threshold){
        it.remove();
    }
0 голосов
/ 02 марта 2009

Попробуйте либо CopyOnWriteArrayList, либо CopyOnWriteArraySet в зависимости от того, что вы пытаетесь сделать.

...