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)
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()
.
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
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
- wyjątekmoże być rzucany, nie musi być rzucany
- różne wątki nie są wymagane
- rzucanie wyjątek nie może być zagwarantowany
- rzucanie wyjątku jest na zasadzie najlepszego wysiłku
- wyrzucenie wyjątku dzieje się gdy jednoczesna modyfikacja jest wykryta , a nie gdy jest to spowodowane
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
rzucaConcurrentModificationException
. 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ętlifor
nadCollection
. To, że nie widziszIterator
obiektu w swoim kodzie źródłowym, nie oznacza, że nie ma tamIterator
! Na szczęście jedno ze stwierdzeń błędnej pętlifor
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 przezCollections.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 yourCollection
, np. sub lists,Map
zestawy wejściowe orazMap
zestawy kluczy zachowują również odniesienia do oryginału (można modyfikować)Collection
. Może to być problem nawet dla thread-safeCollection
, takich jakCopyOnWriteList
; 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.
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);
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.
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ć.
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();
}
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.
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