web-dev-qa-db-de.com

Warum wird eine ConcurrentModificationException ausgelöst und wie wird sie behoben?

Ich verwende ein Collection (ein HashMap, das indirekt von der JPA verwendet wird), aber anscheinend wirft der Code zufällig ein ConcurrentModificationException. Was ist die Ursache und wie behebe ich dieses Problem? Vielleicht durch Synchronisation?

Hier ist der vollständige 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)
112
mainstringargs

Dies ist kein Synchronisationsproblem. Dies tritt auf, wenn die zugrunde liegende Auflistung, über die iteriert wird, von etwas anderem als dem Iterator selbst geändert wird.

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

Dies löst eine ConcurrentModificationException aus, wenn it.hasNext () zum zweiten Mal aufgerufen wird.

Der richtige Ansatz wäre

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

Angenommen, dieser Iterator unterstützt die Operation remove ().

243
Robin

Versuchen Sie, eine ConcurrentHashMap anstelle einer einfachen HashMap zu verwenden

60
Chochos

Änderung eines Collection während des Durchlaufens dieses Collection mit einem Iterator ist nicht erlaubt von den meisten Klassen Collection. Die Bibliothek Java) ruft den Versuch auf, ein Collection zu ändern, während sie eine "gleichzeitige Änderung" durchläuft, was leider darauf hindeutet, dass die einzige mögliche Ursache die gleichzeitige Änderung durch mehrere Threads ist das ist nicht so. Mit nur einem Thread ist es möglich, einen Iterator für den Collection zu erstellen (mit Collection.iterator() oder einem verbesserte for Schleife ), starte die Iteration (mit Iterator.next() , oder gib gleichwertig die Body der erweiterten for Schleife), ändern Sie die Collection und fahren Sie dann mit der Iteration fort.

Um Programmierern zu helfen, einige Implementierungen dieser Collection Klassen versuchen fehlerhafte gleichzeitige Änderungen zu erkennen und werfen ein ConcurrentModificationException wenn sie es erkennen. Es ist jedoch im Allgemeinen nicht möglich und praktisch, die Erkennung aller gleichzeitigen Änderungen zu gewährleisten. Eine fehlerhafte Verwendung von Collection führt nicht immer zum Auslösen von ConcurrentModificationException.

In der Dokumentation von ConcurrentModificationException heißt es:

Diese Ausnahme kann von Methoden ausgelöst werden, die eine gleichzeitige Änderung eines Objekts festgestellt haben, wenn eine solche Änderung nicht zulässig ist ...

Beachten Sie, dass diese Ausnahme nicht immer darauf hinweist, dass ein Objekt gleichzeitig von einem anderen Thread geändert wurde. Wenn ein einzelner Thread eine Sequenz von Methodenaufrufen ausgibt, die den Vertrag eines Objekts verletzt, kann das Objekt diese Ausnahme auslösen ...

Beachten Sie, dass ein fehlerfreies Verhalten nicht garantiert werden kann, da es im Allgemeinen unmöglich ist, bei nicht synchronisierten gleichzeitigen Änderungen harte Garantien zu geben. Fehlgeschlagene Operationen werfen ConcurrentModificationException nach besten Kräften.

Beachten Sie, dass

Die Dokumentation der HashSet , HashMap , Die Klassen TreeSet und ArrayList sagen Folgendes:

Die Iteratoren, die [direkt oder indirekt von dieser Klasse] zurückgegeben werden, sind fehlerfrei: Wenn die [Sammlung] zu irgendeinem Zeitpunkt nach der Erstellung des Iterators geändert wird, außer durch die eigene Methode zum Entfernen des Iterators, wird Iterator wirft ein ConcurrentModificationException. Daher versagt der Iterator trotz gleichzeitiger Modifikation schnell und fehlerfrei, anstatt willkürliches, nicht deterministisches Verhalten zu einem unbestimmten Zeitpunkt in der Zukunft zu riskieren.

Beachten Sie, dass das Fail-Fast-Verhalten eines Iterators nicht garantiert werden kann, da es im Allgemeinen unmöglich ist, bei nicht synchronisierten gleichzeitigen Änderungen harte Garantien zu geben. Fail-Fast-Iteratoren werfen ConcurrentModificationException nach besten Kräften. Daher wäre es falsch, ein Programm zu schreiben, dessen Korrektheit von dieser Ausnahme abhängt: Das Fail-Fast-Verhalten von Iteratoren sollte nur zum Erkennen von Fehlern verwendet werden .

Beachten Sie erneut, dass das Verhalten "nicht garantiert werden kann" und nur "auf bestmöglicher Basis" ist.

Die Dokumentation einiger Methoden der Schnittstelle Map lautet wie folgt:

Nicht gleichzeitig ablaufende Implementierungen sollten diese Methode überschreiben und nach besten Kräften ein ConcurrentModificationException auslösen, wenn festgestellt wird, dass die Zuordnungsfunktion diese Zuordnung während der Berechnung ändert. Gleichzeitige Implementierungen sollten diese Methode überschreiben und nach besten Kräften ein IllegalStateException ausgeben, wenn festgestellt wird, dass die Zuordnungsfunktion diese Zuordnung während der Berechnung ändert und die Berechnung daher niemals abgeschlossen wird.

Beachten Sie erneut, dass nur eine "Best-Effort-Basis" für die Erkennung erforderlich ist und ein ConcurrentModificationException nur für nicht gleichzeitige (nicht thread-sichere) Klassen ausdrücklich empfohlen wird.

Debuggen von ConcurrentModificationException

Wenn Sie also einen Stack-Trace aufgrund eines ConcurrentModificationException sehen, können Sie nicht sofort davon ausgehen, dass die Ursache ein unsicherer Multithread-Zugriff auf ein Collection ist. Sie müssen den Stack-Trace untersuchen , um festzustellen, welche Klasse von Collection die Ausnahme ausgelöst hat (eine Methode der Klasse hat sie direkt oder indirekt ausgelöst), und für welches Collection Objekt. Dann müssen Sie prüfen, von wo dieses Objekt geändert werden kann.

  • Die häufigste Ursache ist die Änderung der Collection innerhalb einer erweiterten for Schleife über der Collection. Nur weil Sie kein Iterator -Objekt in Ihrem Quellcode sehen, heißt das nicht, dass es kein Iterator gibt! Glücklicherweise befindet sich eine der Anweisungen der fehlerhaften for -Schleife normalerweise im Stack-Trace, sodass es normalerweise einfach ist, den Fehler aufzuspüren.
  • Ein schwierigerer Fall ist, wenn Ihr Code Verweise auf das Objekt Collection weitergibt. Beachten Sie, dass unmodifizierbar Ansichten von Sammlungen (wie sie von Collections.unmodifiableList() erstellt wurden) einen Verweis auf die modifizierbare Sammlung behalten Daher kann die Iteration über eine "nicht änderbare" Sammlung die Ausnahme auslösen (die Änderung wurde an anderer Stelle vorgenommen). Andere views Ihrer Collection, z. B. Unterlisten , Map Eingabesätze und Map Tastensätze behalten auch Verweise auf das Original bei (änderbar) Collection . Dies kann selbst für thread-sichere Collection ein Problem sein, z. B. CopyOnWriteList ; Gehen Sie nicht davon aus, dass thread-sichere (gleichzeitige) Collectins die Ausnahme niemals auslösen können.
  • Welche Operationen ein Collection ändern können, kann in einigen Fällen unerwartet sein. Zum Beispiel ändert LinkedHashMap.get() seine Sammlung .
  • Die schwierigsten Fälle sind, wenn die Ausnahme auf die gleichzeitige Änderung durch mehrere Threads zurückzuführen ist.

Programmierung, um gleichzeitige Änderungsfehler zu vermeiden

Wenn möglich, beschränken Sie alle Verweise auf ein Collection -Objekt, damit es einfacher ist, gleichzeitige Änderungen zu verhindern. Machen Sie das Collection zu einem private -Objekt oder einer lokalen Variablen, und geben Sie von Methoden keine Verweise auf das Collection oder dessen Iteratoren zurück. Es ist dann viel einfacher, all die Stellen zu untersuchen, an denen Collection geändert werden kann. Wenn das Collection von mehreren Threads verwendet werden soll, ist es sinnvoll, sicherzustellen, dass die Threads nur bei entsprechender Synchronisierung und Sperrung auf das Collection zugreifen.

9
Raedwald

Es klingt weniger nach einem Java Synchronisationsproblem als vielmehr nach einem Datenbanksperrproblem.

Ich weiß nicht, ob das Hinzufügen einer Version zu allen persistenten Klassen zu einer Sortierung führt, aber auf diese Weise kann Hibernate exklusiven Zugriff auf Zeilen in einer Tabelle gewähren.

Könnte sein, dass die Isolationsstufe höher sein muss. Wenn Sie "Dirty Reads" zulassen, müssen Sie möglicherweise auf serialisierbar umstellen.

2
duffymo

Beachten Sie, dass die ausgewählte Antwort nicht direkt vor einer Änderung auf Ihren Kontext angewendet werden kann, wenn Sie versuchen, einige Einträge aus der Karte zu entfernen, während Sie die Karte genau wie ich iterieren.

Ich gebe hier nur mein Arbeitsbeispiel für Neulinge, um ihre Zeit zu sparen:

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
ZhaoGang

Versuchen Sie es mit CopyOnWriteArrayList oder CopyOnWriteArraySet, je nachdem, was Sie tun möchten.

0
Javamann