2015-04-18 14 views
25

Perché questo codice non genera uno ConcurrentModificationException? Modifica uno Collection durante l'iterazione, senza utilizzare il metodo Iterator.remove(), che deve essere the only safe way of removing.Perché questo codice non lancia 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); 

ottengo lo stesso risultato se si sostituisce la ArrayList con un LinkedList. Tuttavia se cambio l'elenco in ("A", "B", "C", "D) o solo ("A", "B") ottengo l'eccezione come previsto. Cosa sta succedendo? Sto usando jdk1.8.0_25 se questo è rilevante.

EDIT

ho trovato il seguente link

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

La sezione rilevante è

La soluzione ingenua è quella di aggiungere controlli comodification a hasNext in AbstractList, ma questo raddoppia il costo del controllo della comodificazione. Si scopre che è sufficiente eseguire il test solo sull'ultima iterazione , che non aggiunge praticamente nulla al costo. In altre parole, l'attuale implementazione di hasNext:

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

è sostituita da questa implementazione:

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

Questo cambiamento non verranno effettuate a causa di un organismo di regolamentazione Sun-interno ha respinto. La decisione formale ha indicato che il cambiamento "ha dimostrato che ha un impatto significativo sulla compatibilità sul codice esistente." (Il "impatto compatibilità" è che la correzione ha il potenziale per sostituire malfunzionamento muto con un ConcurrentModificationException.)

+6

Perché 'ConcurrentModificationException' viene gettato su un "best-effort" base –

+3

Eventuali duplicati: [java.util.ConcurrentModificationException non torta quando previsto] (http://stackoverflow.com/questions/ 24980651/java-util-concurrentmodificationexception-not-thrown-when-expected) – Pshemo

+1

Mi piace come il motivo per cui Sun non ha apportato il cambiamento è che potrebbe far sì che alcuni codici errati inizino a lanciare l'eccezione che avrebbe dovuto generare il possibile duplicato – Mshnik

risposta

21

Come regola generale, ConcurrentModificationException s sono gettati in cui la modifica è rilevato, non causato. Se non si accede mai all'iteratore dopo la modifica, non genererà un'eccezione. Questo dettaglio minuscolo rende inaffidabili i valori di ConcurrentModificationException s per rilevare l'uso improprio delle strutture di dati, dato che vengono lanciati solo dopo che il danno è stato eseguito.

Questo scenario non genera uno ConcurrentModificationException perché next() non viene chiamato sull'iteratore creato dopo la modifica.

for-each loop sono davvero iteratori, in modo che il codice è in realtà come questa:

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); 

Considerate la vostra codice in esecuzione sulla lista che hai fornito.Le iterazioni assomigliano:

  1. hasNext() restituisce true, inserire loop, -> si sposta ITER indice 0, stringa = "a", non rimossi
  2. hasNext() restituisce vero, continuano ad anello -> ITER si sposta indice 1 , string = "B", rimosso. strings ora ha lunghezza 2.
  3. hasNext() restituisce false (iter è attualmente all'ultimo indice, non più indici da percorrere), ciclo di uscita.

Così, come ConcurrentModificationException s sono gettati quando una chiamata al next() rileva una che una modifica è stata fatta, questo scenario evita stretto tale eccezione.

Per gli altri due risultati, otteniamo eccezioni. Per "A", "B", "C", "D", dopo aver rimosso "B" siamo ancora in loop, e next() rileva la ConcurrentModificationException, mentre per "A", "B" mi immagino che sia un qualche tipo di ArrayIndexOutOfBounds che viene catturato e ri-gettate come ConcurrentModificationException

+0

Ma sicuramente verrebbe generato se avessero appena controllato la modifica simultanea in 'hasNext()' piuttosto che 'next()'? In che modo si tratta di un "miglior sforzo"? –

+0

@pbabcdefp È "best-effort" perché non c'è alcuna garanzia che una modifica concorrente venga notata da JRE se tale modifica non è sincronizzata. –

+5

@pbabcdefp: ["Base best-effort"] (http://en.wikipedia.org/wiki/Best-effort_delivery) non significa "cercheremo e cercheremo di fare assolutamente tutto ciò che è in nostro potere", come Potresti pensare. Significa che ci proveranno, ma non fanno promesse. – user2357112

9

hasNext nel iteratore di ArrayList è solo

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

Dopo la chiamata remove, l'iteratore è indice 2, e le dimensioni della lista è 2, quindi riporta che l'iterazione è completa. Nessun controllo di modifica simultaneo. Con ("A", "B", "C", "D) o (" A "," B "), l'iteratore non si trova nella nuova fine dell'elenco, pertanto viene chiamato next e che genera l'eccezione .

ConcurrentModificationException s sono solo un aiuto per il debug. non si può contare su di loro.

1

@Tavian Barnes è esattamente a destra. Questa eccezione non può essere garantito per essere gettato se la modifica simultanea in questione è sincronizzato. Citando dal java.util.ConcurrentModification specifica:

Nota che non riescono-veloce comportamento non può essere garantita, poiché è, in generale parlando, impossibl e per fare tutte le dure garanzie in presenza di modifica simultanea non sincronizzata. Le operazioni fail-fast generano ConcurrentModificationException su una base best-effort. Pertanto, è errato scrivere un programma che dipendeva da questa eccezione per sua correttezza: ConcurrentModificationException deve essere utilizzato solo per rilevare i bug.

Link to JavaDoc for ConcurrentModificationException

+0

Si noti che il codice di esempio nel post originale è un singolo thread. Non c'è nessun problema di sincronizzazione qui. Il "concurrent" in 'ConcurrentModificationException' non ha nulla da fare (direttamente) con i thread; significa semplicemente "nello stesso momento in cui è in corso un'iterazione", non "un altro thread ha modificato l'elenco". –

Problemi correlati