Eine Änderung einer Collection
Weile, die durch die Collection
Verwendung von a iteriert, Iterator
ist von den meisten Klassen nicht zulässigCollection
. Die Java-Bibliothek nennt einen Änderungsversuch Collection
während des Durchlaufens eine "gleichzeitige Änderung". Das deutet leider darauf hin, dass die einzig mögliche Ursache die gleichzeitige Änderung durch mehrere Threads ist, aber das ist nicht so. Mit nur einem Thread ist es möglich, einen Iterator für die Collection
(using Collection.iterator()
oder eine erweiterte for
Schleife ) zu erstellen , mit der Iteration zu beginnen (mit Iterator.next()
oder gleichwertig in den Körper der erweiterten for
Schleife einzutreten ), die zu ändern Collection
und dann die Iteration fortzusetzen.
Um Programmierern zu helfen, versuchen einige Implementierungen dieser Collection
Klassen , fehlerhafte gleichzeitige Änderungen zu erkennen, und werfen ein, wenn sie dies erkennen. Es ist jedoch im Allgemeinen nicht möglich und praktisch, die Erkennung aller gleichzeitigen Änderungen zu gewährleisten. Eine fehlerhafte Verwendung des führt also nicht immer zu einem WurfConcurrentModificationException
Collection
ConcurrentModificationException
.
Die Dokumentation von ConcurrentModificationException
sagt:
Diese Ausnahme kann durch 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 anzeigt, dass ein Objekt gleichzeitig von einem anderen Thread geändert wurde. Wenn ein einzelner Thread eine Folge von Methodenaufrufen ausgibt, die gegen den Vertrag eines Objekts verstoßen, kann das Objekt diese Ausnahme auslösen ...
Beachten Sie, dass ein ausfallsicheres Verhalten nicht garantiert werden kann, da es im Allgemeinen unmöglich ist, bei nicht synchronisierten gleichzeitigen Änderungen harte Garantien zu geben. Fail-Fast-Operationen werden ConcurrentModificationException
nach besten Kräften ausgeführt.
Beachten Sie, dass
Die Dokumentation der HashSet
, HashMap
, TreeSet
und ArrayList
Klassen , sagt dazu:
Die [direkt oder indirekt von dieser Klasse] zurückgegebenen Iteratoren sind ausfallsicher: Wenn die [Sammlung] zu irgendeinem Zeitpunkt nach dem Erstellen des Iterators geändert wird, außer durch die eigene Entfernungsmethode des Iterators, werden a Iterator
ausgelöstConcurrentModificationException
. Daher fällt der Iterator angesichts gleichzeitiger Änderungen schnell und sauber aus, anstatt zu einem unbestimmten Zeitpunkt in der Zukunft willkürliches, nicht deterministisches Verhalten zu riskieren.
Beachten Sie, dass das ausfallsichere 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 Richtigkeit von dieser Ausnahme abhängt: Das ausfallsichere Verhalten von Iteratoren sollte nur zur Erkennung von Fehlern verwendet werden .
Beachten Sie erneut, dass das Verhalten "nicht garantiert werden kann" und nur "auf Best-Effort-Basis" ist.
Die Dokumentation mehrerer Methoden der Map
Schnittstelle besagt Folgendes:
Nicht gleichzeitige Implementierungen sollten diese Methode überschreiben und nach bestem Wissen und Gewissen ein auslösen, ConcurrentModificationException
wenn festgestellt wird, dass die Zuordnungsfunktion diese Zuordnung während der Berechnung ändert. Gleichzeitige Implementierungen sollten diese Methode überschreiben und nach bestem Wissen und Gewissen eine auslösen, IllegalStateException
wenn festgestellt wird, dass die Zuordnungsfunktion diese Zuordnung während der Berechnung ändert und die Berechnung daher niemals abgeschlossen werden würde.
Beachten Sie erneut, dass für die Erkennung nur eine "Best-Effort-Basis" erforderlich ist und a ConcurrentModificationException
explizit nur für nicht gleichzeitige (nicht threadsichere) Klassen empfohlen wird.
Debuggen ConcurrentModificationException
Wenn Sie also aufgrund von a einen Stack-Trace sehen ConcurrentModificationException
, können Sie nicht sofort davon ausgehen, dass die Ursache ein unsicherer Multithread-Zugriff auf a ist Collection
. Sie müssen den Stack-Trace untersuchen, um festzustellen, welche Klasse 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 aus das Objekt geändert werden kann.
- Die häufigste Ursache ist die Änderung der
Collection
innerhalb einer erweiterten for
Schleife über die Collection
. Nur weil Sie Iterator
in Ihrem Quellcode kein Objekt sehen, heißt das nicht, dass es dort kein Iterator
gibt! Glücklicherweise befindet sich eine der Anweisungen der fehlerhaften for
Schleife normalerweise in der Stapelverfolgung, sodass das Aufspüren des Fehlers normalerweise einfach ist.
- Ein schwierigerer Fall ist, wenn Ihr Code Verweise auf das
Collection
Objekt weitergibt . Beachten Sie, dass nicht modifizierbare Ansichten von Sammlungen (wie z. B. von Collections.unmodifiableList()
) einen Verweis auf die modifizierbare Sammlung beibehalten, sodass eine Iteration über eine "nicht modifizierbare" Sammlung die Ausnahme auslösen kann (die Änderung wurde an anderer Stelle vorgenommen). Andere Ansichten von Ihnen Collection
, wie Unterlisten , Map
Eintragssätze und Map
Schlüsselsätze, behalten ebenfalls Verweise auf das Original bei (änderbar) Collection
. Dies kann selbst für einen Thread-Safe ein Problem sein Collection
, wie z CopyOnWriteList
. Gehen Sie nicht davon aus, dass threadsichere (gleichzeitige) Sammlungen niemals die Ausnahme auslösen können.
- Welche Operationen a ändern
Collection
können, kann in einigen Fällen unerwartet sein. Ändert beispielsweise LinkedHashMap.get()
die Sammlung .
- Die schwersten Fälle sind , wenn die Ausnahme ist , um gleichzeitige Modifikation von mehreren Threads durch.
Programmierung zur Vermeidung gleichzeitiger Änderungsfehler
Beschränken Sie nach Möglichkeit alle Verweise auf ein Collection
Objekt, damit gleichzeitige Änderungen leichter verhindert werden können. Erstellen Sie Collection
ein private
Objekt oder eine lokale Variable und geben Sie keine Verweise auf die Collection
oder ihre Iteratoren von Methoden zurück. Es ist dann viel einfacher, alle Stellen zu untersuchen, an denen Collection
Änderungen vorgenommen werden können. Wenn das Collection
von mehreren Threads verwendet werden soll, ist es praktisch sicherzustellen, dass die Threads Collection
nur mit entsprechender Synchronisation und Sperrung auf die Threads zugreifen .