ConcurrentModificationException bei Verwendung des Streams mit dem Schlüsselsatz "Maps"

8

Ich möchte alle Elemente aus someMap entfernen, deren Schlüssel nicht in someList vorhanden sind. Schau in meinen Code:

%Vor%

Ich erhalte java.util.ConcurrentModificationException . Warum? Stream ist nicht parallel. Was ist der eleganteste Weg, dies zu tun?

    
jaskmar 15.09.2015, 08:04
quelle

3 Antworten

15

@Eran hat bereits erklärt , wie man dieses Problem besser lösen kann. Ich werde erklären, warum ConcurrentModificationException auftritt.

Das ConcurrentModificationException tritt auf, weil Sie die Stream-Quelle ändern. Ihr Map ist wahrscheinlich HashMap oder TreeMap oder eine andere nicht gleichzeitige Karte. Nehmen wir an, es ist ein HashMap . Jeder Stream wird von Spliterator unterstützt. Wenn der Spliterator keine Eigenschaften IMMUTABLE und CONCURRENT hat, lautet die Dokumentation wie folgt:

  

Nach der Bindung sollte ein Spliterator auf bestmöglicher Basis ConcurrentModificationException werfen, wenn strukturelle Interferenzen erkannt werden. Spliter, die dies tun, werden fail-fast genannt.

Also ist das HashMap.keySet().spliterator() nicht IMMUTABLE (weil dieses Set geändert werden kann) und nicht CONCURRENT (gleichzeitige Updates sind für HashMap unsicher). Daher werden nur die gleichzeitigen Änderungen erkannt und ein ConcurrentModificationException ausgegeben, wie es die Dokumentation für den Spliterator vorschreibt.

Es lohnt sich auch, die HashMap Dokumentation

  

Die Iteratoren, die von allen "Auflistungsansichtsmethoden" dieser Klasse zurückgegeben werden, sind fail-fast : wenn die Map zu irgendeinem Zeitpunkt nach der Erstellung des Iterators strukturell modifiziert wird, außer durch den Iterator eigene Methode entfernen, wirft der Iterator eine ConcurrentModificationException . Angesichts der gleichzeitigen Modifikation versagt der Iterator daher schnell und sauber, 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 unsynchronisierten simultanen Modifikationen harte Garantien zu geben. Fail-Fast-Iteratoren werfen ConcurrentModificationException auf Best-Effort-Basis. Daher wäre es falsch, ein Programm zu schreiben, das für seine Korrektheit von dieser Ausnahme abhing: Das Fail-Fast-Verhalten von Iteratoren sollte nur zum Aufspüren von Fehlern verwendet werden .

Obwohl es nur über Iteratoren aussagt, glaube ich, dass es bei Spliteratoren genauso ist.

    
Tagir Valeev 15.09.2015, 09:00
quelle
8

Sie benötigen dafür nicht die Stream API. Verwende retainAll für keySet . Alle Änderungen an Set , die von keySet() zurückgegeben werden, spiegeln sich in der ursprünglichen Map wider.

%Vor%     
Eran 15.09.2015 08:06
quelle
3

Ihr Stream-Aufruf führt (logisch) Folgendes aus:

%Vor%

Wenn Sie dies ausführen, wird es ConcurrentModificationException ausgeben, weil es die Karte zur gleichen Zeit verändert, wenn Sie darüber iterieren. Wenn Sie sich die Dokumentation ansehen, werden Sie Folgendes bemerken:

  

Beachten Sie, dass diese Ausnahme nicht immer anzeigt, 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 verletzen, kann das Objekt diese Ausnahme auslösen. Wenn beispielsweise ein Thread eine Auflistung direkt ändert, während sie die Auflistung mit einem Fail-Fast-Iterator wiederholt, löst der Iterator diese Ausnahme aus.

Dies ist, was Sie tun, die Map-Implementierung, die Sie verwenden, hat offensichtlich schnelle Iteratoren, daher wird diese Ausnahme ausgelöst.

Eine mögliche Alternative besteht darin, die Elemente mit dem Iterator direkt zu entfernen:

%Vor%     
thecoop 15.09.2015 08:57
quelle