2014-11-28 3 views
5

Betrachten Sie den folgenden Code-Schnipsel:Warum keine ConcurrentModificationException in dieser Situation mit LinkedList-Iterator?

List<String> list = new LinkedList<>(); 
list.add("Hello"); 
list.add("My"); 
list.add("Son"); 

for (String s: list){ 
    if (s.equals("My")) list.remove(s); 
    System.out.printf("s=%s, list=%s\n",s,list.toString()); 
} 

Dies resultiert in der Ausgabe:

s = Hallo, list = [Hallo, mein, Sohn]
s = My, list = [Hallo, Sohn]

So deutlich wurde die Schleife nur zweimal eingegeben, und das dritte Element, "Son", wird nie besucht. Aus dem zugrunde liegenden Bibliothekscode sieht es so aus, als ob die hasNext()-Methode im Iterator nicht nach gleichzeitiger Änderung sucht, sondern nur nach der Größe gegenüber dem nächsten Index. Da die Größe durch den Aufruf remove() um 1 reduziert wurde, wird die Schleife einfach nicht erneut eingegeben, aber es wird keine ConcurrentModificationException ausgelöst.

Dies scheint den Vertrag des Iterators zu widersprechen:

Die Liste-Iterator ist ausfall schnell: wenn die Liste strukturell jederzeit geändert, nachdem der Iterator, außer in irgendeiner Weise erstellt wird Durch die eigenen Methoden remove oder add des Listen-Iterators wirft der Listen-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.

Ist das ein Fehler? Auch hier scheint der Vertrag des Iterators definitiv nicht befolgt zu werden - die Struktur der Liste wird strukturell durch etwas anderes als den Iterator in der Mitte der Iteration modifiziert.

+0

Einige Leute verweisen auf die Dokumentation der LinkedList-Klassenebene, die einige Hinweise darauf gibt, dass die ConcurrentModificationException auf "Best Effort" -Basis ist. Es sei denn, jemand kann beweisen, warum das Hinzufügen zu "hasNext()" aus irgendeinem Grund problematisch ist, es scheint nicht die beste Anstrengung überhaupt zu sein. – PeteyPabPro

+0

Ich könnte hier das Offensichtliche angeben, aber der einzige Grund, warum ich mir vorstellen kann, die Prüfung für die Änderung in der 'hasNext()' Methode nicht zu implementieren, ist, dass es eine Verdoppelung der Prüfvorgänge in * allen * Fällen während Abwesenheit garantiert Eine solche Überprüfung führt nur in einigen Fällen zum Nicht-Auslösen der Ausnahme, wenn die durchgeführte Änderung zum sofortigen Verlassen der Schleife führt (z. B. wenn alle Elemente von der aktuellen Position des Iterators bis zum Ende des Containers entfernt werden). , was von den Implementierern als relativ selten angesehen wurde. –

+0

Scheint ein Duplikat von http://stackoverflow.com/questions/14673653/why-isnt-this-code-causing-a-concurrentmodificationexception?lq=1 (wie in einer Antwort) – Marco13

Antwort

3

Lesen Sie das Klasse-Ebene Javadoc:

Beachten Sie, dass der Ausfall schnell Verhalten eines Iterators nicht, wie es ist gewährleistet werden kann, allgemein gesprochen, unmöglich, irgendwelche harten Garantien in Gegenwart von unsynchronisierten gleichzeitiger Modifikation zu machen . 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 abhängig ist: Das Fail-Fast-Verhalten von Iteratoren sollte nur zum Erkennen von Fehlern verwendet werden.

+0

Oder ist es das Feature des Compilers, der Iterator darunter verwendet? – mprabhat

+0

Es hat nichts damit zu tun, dass der Compiler und alles, was damit zu tun hat, unmöglich oder zumindest extrem unpraktisch ist. –

+0

Lassen Sie mich den Quellcode öffnen, um zu sehen, was dort ist – mprabhat

3

Von https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html:

Beachten Sie, dass der Ausfall schnell Verhalten eines Iterators nicht garantiert werden, da es im Allgemeinen unmöglich, eine harte Garantie in Gegenwart von unsynchronisierten gleichzeitiger Modifikation zu machen . Fail-Fast Iteratoren werfen ConcurrentModificationException auf einer Best-Effort-Basis . Daher wäre es falsch, ein Programm zu schreiben, das auf diese Ausnahme wegen seiner Korrektheit abhing: das Fail-Fast-Verhalten von Iteratoren sollte nur verwendet werden, um Fehler zu entdecken.

Das heißt, der Iterator versucht sein Bestes, um eine Ausnahme auszulösen, aber es ist nicht in allen Fällen garantiert.

Hier sind einige weitere Links, wie schnell Iteratoren funktioniert nicht und wie das umgesetzt werden - für den Fall, wird jemand interessieren:

http://www.certpal.com/blogs/2009/09/iterators-fail-fast-vs-fail-safe/

http://javahungry.blogspot.com/2014/04/fail-fast-iterator-vs-fail-safe-iterator-difference-with-example-in-java.html

http://www.javaperformancetuning.com/articles/fastfail2.shtml

Und hier ist eine andere SO Frage, wo Leute versuchen, die gleiche Sache herauszufinden:

Why isn't this code causing a ConcurrentModificationException?

+0

Ich wollte nur das Gleiche zitieren. Es könnte sich lohnen, einen Fehlerbericht zu erstellen, aber Sie können sich nicht darauf verlassen, dass eine Ausnahme ausgelöst wird. – markspace

+0

@markspace: Nun, die Fähigkeit des Iterators, eine Ausnahme auszulösen, ist definitiv nützlich und kann Ihnen in vielen Fällen Ärger ersparen, aber es ist immer noch keine Garantie und bedeutet nicht, dass Sie nicht versuchen sollten, Änderungen zu vermeiden Auf unpassende Weise auflisten. Ich schätze, es ist mehr wie ein Sicherheitsgurt: Sie tragen es besser, aber es bedeutet nicht, dass Sie mit 60 Meilen pro Stunde sicher in den Laternenpfahl fahren. –

+0

@striving_coder es versucht nicht sein Bestes. Während es in komplizierten Situationen, die mehrere Threads beinhalten, unmöglich sein kann, dies zu garantieren, ist dies ein so grundlegender Anwendungsfall wie möglich. – PeteyPabPro

Verwandte Themen