Eine Änderung einer CollectionWeile, die durch die CollectionVerwendung von a iteriert, Iteratorist von den meisten Klassen nicht zulässigCollection . Die Java-Bibliothek nennt einen Änderungsversuch Collectionwä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 forSchleife ) zu erstellen , mit der Iteration zu beginnen (mit Iterator.next()oder gleichwertig in den Körper der erweiterten forSchleife einzutreten ), die zu ändern Collectionund dann die Iteration fortzusetzen.
Um Programmierern zu helfen, versuchen einige Implementierungen dieser CollectionKlassen , 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 WurfConcurrentModificationExceptionCollectionConcurrentModificationException .
Die Dokumentation von ConcurrentModificationExceptionsagt:
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 ConcurrentModificationExceptionnach besten Kräften ausgeführt.
Beachten Sie, dass
Die Dokumentation der HashSet, HashMap, TreeSetund ArrayListKlassen , 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 Iteratorausgelö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 ConcurrentModificationExceptionnach 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 MapSchnittstelle besagt Folgendes:
Nicht gleichzeitige Implementierungen sollten diese Methode überschreiben und nach bestem Wissen und Gewissen ein auslösen, ConcurrentModificationExceptionwenn 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, IllegalStateExceptionwenn 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 ConcurrentModificationExceptionexplizit 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 Collectiondie Ausnahme ausgelöst hat (eine Methode der Klasse hat sie direkt oder indirekt ausgelöst) und für welches CollectionObjekt. Dann müssen Sie prüfen, von wo aus das Objekt geändert werden kann.
- Die häufigste Ursache ist die Änderung der
Collectioninnerhalb einer erweiterten forSchleife über die Collection. Nur weil Sie Iteratorin Ihrem Quellcode kein Objekt sehen, heißt das nicht, dass es dort kein Iteratorgibt! Glücklicherweise befindet sich eine der Anweisungen der fehlerhaften forSchleife normalerweise in der Stapelverfolgung, sodass das Aufspüren des Fehlers normalerweise einfach ist.
- Ein schwierigerer Fall ist, wenn Ihr Code Verweise auf das
CollectionObjekt 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 , MapEintragssätze und MapSchlü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
Collectionkö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 CollectionObjekt, damit gleichzeitige Änderungen leichter verhindert werden können. Erstellen Sie Collectionein privateObjekt oder eine lokale Variable und geben Sie keine Verweise auf die Collectionoder 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 Collectionvon mehreren Threads verwendet werden soll, ist es praktisch sicherzustellen, dass die Threads Collectionnur mit entsprechender Synchronisation und Sperrung auf die Threads zugreifen .