2015-04-18 14 views
25

Warum wirft dieser Code keine ConcurrentModificationException? Es ändert ein Collection während es durchlaufen, ohne die Iterator.remove() Methode zu verwenden, die the only safe way of removing sein soll.Warum wirft dieser Code keine ConcurrentModificationException?

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C")); 
for (String string : strings) 
    if ("B".equals(string)) 
     strings.remove("B"); 
System.out.println(strings); 

ich das gleiche Ergebnis, wenn ich die ArrayList mit einem LinkedList ersetzen. Wenn ich jedoch die Liste zu ("A", "B", "C", "D) oder nur ("A", "B") ändere, bekomme ich die Ausnahme wie erwartet. Was ist los? Ich verwende jdk1.8.0_25, wenn das relevant ist.

EDIT

ich den folgenden Link gefunden habe

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078

Der entsprechende Abschnitt ist

Die naive Lösung Kommodifizierung Kontrollen hasNext in AbstractList hinzuzufügen ist, aber das verdoppelt die Kosten für die Überprüfung der Kompatibilität. Es stellt sich heraus, dass es ausreichend ist, den Test nur auf der letzten Iteration zu machen, die praktisch nichts zu den Kosten hinzufügt. Mit anderen Worten, die aktuelle Implementierung von hasNext:

public boolean hasNext() { 
     return nextIndex() < size; 
    } 

durch diese Implementierung ersetzt:

public boolean hasNext() { 
     if (cursor != size()) 
      return true; 
     checkForComodification(); 
     return false; 
    } 

Diese Änderung vorgenommen wird, nicht weil eine Sun-interne Aufsichtsbehörde es abgelehnt. Das formelle Urteil hat gezeigt, dass die Änderung " das Potenzial für eine erhebliche Kompatibilität Auswirkungen auf bestehenden Code gezeigt hat". (Die „Auswirkungen auf der Kompatibilität“ sind, dass das Update das Potenzial mit einer ConcurrentModificationException zu ersetzen stilles Fehlverhalten hat.)

+6

Weil 'ConcurrentModificationException' auf einem "Best-Effort" -Basis –

+3

Mögliche doppelte geworfen 24980651/java-util-concurrentmodificationexception-nicht-geworfen-wann-erwartet) – Pshemo

+1

Ich liebe, wie der Grund, warum Sun die Änderung nicht gemacht hat ist, dass es einige schlechten Code tatsächlich starten könnte werfen die Ausnahme, die es – Mshnik

Antwort

21

Als allgemeine Regel wird ConcurrentModificationException s ausgelöst, wenn die Modifikation erkannt, nicht verursacht. Wenn Sie nach der Änderung nie auf den Iterator zugreifen, wird keine Ausnahme ausgelöst. Dieses winzige Detail macht ConcurrentModificationException s ziemlich unzuverlässig für das Entdecken von Mißbrauch von Datenstrukturen, leider, weil sie nur geworfen werden, nachdem der Schaden getan worden ist.

Dieses Szenario wirft keine ConcurrentModificationException, weil next() nicht auf dem erstellten Iterator nach der Änderung aufgerufen wird.

foreach-Schleifen wirklich Iteratoren sind, also eigentlich Ihr Code wie folgt aussieht:

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C")); 
Iterator<String> iter = strings.iterator(); 
while(iter.hasNext()){ 
    String string = iter.next(); 
    if ("B".equals(string)) 
     strings.remove("B"); 
} 
System.out.println(strings); 

Betrachten Sie den Code auf der Liste laufen Sie zur Verfügung gestellt.Die Iterationen wie folgt aussehen:

  1. hasNext() gibt true zurück, Schleife eingeben, -> iter bewegt sich der Index 0, string = "A", entfernt nicht
  2. hasNext() true zurück, Schleife weiter -> iter bewegt zu Index 1 , string = "B", entfernt. strings hat jetzt Länge 2.
  3. hasNext() gibt false zurück (iter befindet sich aktuell am letzten Index, keine weiteren Indizes), exit loop. So

, wie ConcurrentModificationException s werden ausgelöst, wenn ein Aufruf an next() ein erkennt, dass eine Änderung vorgenommen wurde, dieses Szenario nur knapp eine solche Ausnahme vermeidet.

Für Ihre anderen zwei Ergebnisse erhalten wir Ausnahmen. Für "A", "B", "C", "D" nach „B“ wir sind noch in der Schleife zu entfernen, und next() erkennt die ConcurrentModificationException, während für "A", "B" Ich könnte mir vorstellen, es ist eine Art von ArrayIndexOutOfBounds ist, die gefangen ist und als ConcurrentModificationException

+0

Aber sicher würde es geworfen werden, wenn sie nur in 'hasNext()' anstelle von 'next()' auf gleichzeitige Änderung überprüft hätten? Wie ist das ein "Best-Effort"? –

+0

@pbabcdefp Dies ist "Best-Effort", da es keine Garantie gibt, dass eine gleichzeitige Änderung von der JRE bemerkt wird, wenn diese Änderung nicht synchronisiert ist. –

+5

@pbabcdefp: ["Best-Effort-Basis"] (http://en.wikipedia.org/wiki/Best-effort_delivery) bedeutet nicht "wir werden versuchen und versuchen und absolut alles in unserer Macht stehende tun", wie du denkst vielleicht. Es bedeutet, dass sie es versuchen werden, aber sie versprechen nichts. – user2357112

9

hasNext in der erneut geworfen Arraylist des Iterators ist nur

public boolean hasNext() { 
    return cursor != size; 
} 

Nach dem remove Aufruf ist der Iterator bei Index 2 und die Größe der Liste 2, so meldet es, dass die Iteration abgeschlossen ist. Keine gleichzeitige Änderungsprüfung. Mit ("A", "B", "C", "D" oder ("A", "B") befindet sich der Iterator nicht am neuen Ende der Liste, so dass next aufgerufen wird und die Ausnahme ausgelöst wird

. sind

ConcurrentModificationException s nur eine Debugging-Hilfe. Sie nicht auf sie verlassen können.

1

@Tavian Barnes genau richtig ist. Diese Ausnahme nicht, wenn die gleichzeitige Änderung in Frage unsynchronisiert ist geworfen garantiert werden kann, werden. von der Zitiert java.util.ConcurrentModification Spezifikation:

Beachten sie, dass verhalten schnell ausfallen kann nicht garantiert werden, wie es ist, in der Regel gesprochen, Impossibl e, um irgendwelche harten Garantien in Anwesenheit von unsynchronized gleichzeitige Änderung zu machen. Fail-Fast-Operationen werfen ConcurrentModificationException auf Best-Effort-Basis. Daher wäre es falsch, ein Programm zu schreiben, das von dieser Ausnahme für seine Korrektheit abhängig ist: ConcurrentModificationException sollte nur verwendet werden, um Fehler zu erkennen. [Java.util.ConcurrentModificationException nicht ausgelöst, wenn zu erwarten] (http://stackoverflow.com/questions/:

Link to JavaDoc for ConcurrentModificationException

+0

Beachten Sie, dass der Beispielcode in dem ursprünglichen Post ein einzelner Thread ist. Es gibt kein Synchronisationsproblem hier. Die "gleichzeitige" in "ConcurrentModificationException" hat nichts (direkt) mit Threads zu tun; es bedeutet einfach "zur gleichen Zeit, dass eine Iteration im Gange war", nicht "ein anderer Thread änderte die Liste". –

Verwandte Themen