Dlaczego rzuca się ConcurrentModificationException i jak go debugować

Używam Collection (A HashMap używane pośrednio przez JPA, tak się zdarza), ale najwyraźniej przypadkowo kod rzuca ConcurrentModificationException. Co jest przyczyną i jak rozwiązać ten problem? Może przez synchronizację?

Oto pełny stack-trace:

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)
Author: Raedwald, 2009-03-02

8 answers

To nie jest problem z synchronizacją. Dzieje się tak, jeśli zbiór bazowy, który jest iterowany, jest modyfikowany przez cokolwiek innego niż sam Iterator.

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

To rzuci ConcurrentModificationException, Gdy it.hasNext() zostanie wywołany po raz drugi.

Poprawne podejście to

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

Zakładając, że iterator obsługuje operację remove().

 267
Author: Robin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-02 15:23:51

Spróbuj użyć ConcurrentHashMap zamiast zwykłego HashMap

 78
Author: Chochos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-02 15:45:33

Modyfikacja a Collection podczas iteracji Collection za pomocą Iterator jest niedozwolone przez większość klas Collection. Biblioteka Java wywołuje próbę zmodyfikowania Collection podczas iteracji poprzez nią "jednoczesnej modyfikacji". To niestety sugeruje, że jedyną możliwą przyczyną jest jednoczesna modyfikacja przez wiele wątków, ale tak nie jest. Używając tylko jednego wątku można utworzyć iterator dla Collection (używając Collection.iterator(), lub an enhanced for loop ), start iterating( using Iterator.next(), lub równoważnie wchodząc do ciała rozszerzonej pętli for), zmodyfikować Collection, a następnie kontynuować iterację.

Aby pomóc programistom, niektóre implementacje tych klas Collection próbują wykryć błędne współbieżne modyfikacje i rzucająConcurrentModificationException, jeśli je wykryją. Jednak zasadniczo nie jest możliwe i praktyczne zagwarantowanie wykrycia wszystkich jednoczesnych modyfikacji. Więc błędne użycie Collection nie zawsze skutkuje wyrzuceniem ConcurrentModificationException.

Dokumentacja ConcurrentModificationException says:

Ten wyjątek może być wyrzucany przez metody, które wykryły jednoczesną modyfikację obiektu, jeśli taka modyfikacja nie jest dozwolona...

Zauważ, że ten wyjątek nie zawsze oznacza, że obiekt został jednocześnie zmodyfikowany przez inny wątek. Jeśli pojedynczy wątek wyda sekwencję wywołań metody, która narusza umowa obiektu, obiekt może wyrzucić ten wyjątek...

Zauważ, że nie można zagwarantować szybkiego działania, ponieważ ogólnie rzecz biorąc, nie można dokonywać żadnych twardych gwarancji w obecności niezsynchronizowanej jednoczesnej modyfikacji. Operacje bezawaryjne rzucają ConcurrentModificationException na zasadzie najlepszego wysiłku.

Zauważ, że

Dokumentacja HashSet, HashMap, TreeSet oraz ArrayList klasy mówią tak:

Iteratory zwracane [bezpośrednio lub pośrednio z tej klasy] są Fail-fast: if [kolekcja] jest modyfikowana w dowolnym momencie po utworzeniu iteratora, w jakikolwiek sposób, z wyjątkiem własnej metody usuwania iteratora, Iterator rzuca ConcurrentModificationException. Tak więc, w obliczu jednoczesnej modyfikacji, iterator zawodzi szybko i czysto, zamiast ryzykować arbitralne, niedeterministyczne zachowanie w nieokreślonym czasie w przyszłości.

Zauważ, że nie można zagwarantować szybkiego działania iteratora, ponieważ ogólnie rzecz biorąc, nie można tworzyć żadnych twardych gwarancji w obecność niezsynchronizowanej jednoczesnej modyfikacji. Iteratory Fail-fast rzucają ConcurrentModificationException na zasadzie najlepszego wysiłku. Dlatego błędem byłoby napisanie programu, który zależałby od tego wyjątku pod względem poprawności: szybkie działanie iteratorów powinno być używane tylko do wykrywania błędów .

Zauważ ponownie, że zachowanie "nie może być zagwarantowane" i jest tylko "na zasadzie najlepszego wysiłku".

Dokumentacja kilku metod Map interfejs say to:

Implementacje nie-współbieżne powinny nadpisać tę metodę i, na zasadzie najlepszego wysiłku, rzucać ConcurrentModificationException, jeśli zostanie wykryte, że funkcja mapowania modyfikuje tę mapę podczas obliczeń. Współbieżne implementacje powinny nadpisać tę metodę i, z największym wysiłkiem, rzucić IllegalStateException, jeśli zostanie wykryte, że funkcja mapowania modyfikuje tę mapę podczas obliczeń i w rezultacie obliczenia nigdy się nie zakończą.

Zauważ jeszcze raz, że tylko " najlepszy wysiłek basis " jest wymagana do wykrywania, a ConcurrentModificationException jest wyraźnie sugerowana tylko dla klas non concurrent (non thread-safe).

Debugowanie ConcurrentModificationException

Tak więc, gdy widzisz stos-trace z powodu ConcurrentModificationException, nie możesz od razu założyć, że przyczyną jest niebezpieczny dostęp wielowątkowy do Collection. Musisz zbadać stos-trace aby określić, która klasa Collection wyrzuciła wyjątek (metoda klasy będzie miała bezpośrednio lub pośrednio wyrzucony), i dla której Collection obiekt. Następnie musisz sprawdzić, skąd ten obiekt może zostać zmodyfikowany.

  • najczęstszą przyczyną jest modyfikacja Collection wewnątrz rozszerzonej pętli for nad Collection. To, że nie widzisz Iterator obiektu w swoim kodzie źródłowym, nie oznacza, że nie ma tam Iterator! Na szczęście jedno ze stwierdzeń błędnej pętli for zwykle znajduje się w stos-trace, więc śledzenie błędu jest zwykle łatwe.
  • trudniejsza sprawa jest wtedy, gdy twój kod przechodzi wokół odwołania do obiektu Collection. Zauważ, że niezmodyfikowane widoki kolekcji (takie jak produkowane przez Collections.unmodifiableList()) zachowaj odniesienie do kolekcji modyfikowalnej, więc iteracja nad kolekcją "niemodyfikowalną" może wyrzucić wyjątek (modyfikacja została dokonana gdzie indziej). Inne views of your Collection, np. sub lists, Map zestawy wejściowe oraz Map zestawy kluczy zachowują również odniesienia do oryginału (można modyfikować) Collection. Może to być problem nawet dla thread-safe Collection, takich jak CopyOnWriteList; nie należy zakładać, że zbiory bezpieczne dla wątków (współbieżne) nigdy nie mogą wyrzucić wyjątku.
  • które operacje mogą zmodyfikować Collection mogą być nieoczekiwane w niektórych przypadkach. Na przykład, LinkedHashMap.get() modyfikuje swój zbiór .
  • najtrudniejsze są przypadki, gdy wyjątek jest Z powodu jednoczesnej modyfikacji przez wiele wątków.

Programowanie zapobiegające współbieżności błędy modyfikacji

Jeśli to możliwe, ogranicz wszystkie odwołania do obiektu Collection, aby łatwiej było zapobiec jednoczesnym modyfikacjom. Niech Collection stanie się obiektem private lub zmienną lokalną i nie zwraca odwołań do Collection lub jego iteratorów z metod. Wtedy znacznie łatwiej jest zbadać wszystkie miejsca, w których można modyfikować Collection. Jeśli Collection ma być używany przez wiele wątków, praktyczne jest upewnienie się, że wątki uzyskują dostęp do Collection tylko za pomocą odpowiedniego synchronizacja i blokowanie.

 21
Author: Raedwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

W Javie 8 można użyć wyrażenia lambda:

map.keySet().removeIf(key -> key condition);
 5
Author: Zentopia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-20 07:08:47

Brzmi to mniej jak problem z synchronizacją w Javie, a bardziej jak problem z blokowaniem bazy danych.

Nie wiem, czy dodanie wersji do wszystkich klas trwałych to rozwiąże, ale jest to jeden ze sposobów, w jaki Hibernate może zapewnić wyłączny dostęp do wierszy w tabeli.

Możliwe, że poziom izolacji musi być wyższy. Jeśli zezwalasz na "brudne odczyty", może musisz przejść do serializowalnego.

 2
Author: duffymo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 15:04:28

Spróbuj albo CopyOnWriteArrayList lub CopyOnWriteArraySet w zależności od tego, co próbujesz zrobić.

 0
Author: Javamann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 16:04:01

Zauważ, że wybrana odpowiedź nie może być zastosowana do twojego kontekstu bezpośrednio przed jakąś modyfikacją, jeśli próbujesz usunąć niektóre wpisy z mapy podczas iteracji mapy tak jak ja.

Podaję tutaj mój przykład pracy dla początkujących, aby zaoszczędzić ich czas:

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
Author: ZhaoGang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-06 08:28:18

Napotkałem ten wyjątek podczas próby usunięcia x ostatnich pozycji z listy. myList.subList(lastIndex, myList.size()).clear(); to jedyne rozwiązanie, które mi się udało.

 0
Author: grayman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-15 14:03:30