La modifica di un Collection
po 'che scorre attraverso l' Collection
uso di un nonIterator
è consentita dalla maggior parte delle Collection
classi. La libreria Java chiama un tentativo di modifica Collection
mentre esegue ripetutamente una "modifica simultanea". Ciò sfortunatamente suggerisce che l'unica causa possibile è la modifica simultanea di più thread, ma non è così. Utilizzando un solo thread è possibile creare un iteratore per Collection
(usando Collection.iterator()
, o un ciclo avanzatofor
), iniziare iterando (usando Iterator.next()
o inserendo in modo equivalente il corpo del for
ciclo migliorato ), modificare Collection
, quindi continuare iterando.
Per aiutare i programmatori, alcune implementazioni di quelle Collection
classi tentano di rilevare modifiche simultanee errate e lanciano un ConcurrentModificationException
se la rilevano. Tuttavia, in generale non è possibile e pratico garantire il rilevamento di tutte le modifiche simultanee. Quindi un uso errato del Collection
non sempre provoca un lancio ConcurrentModificationException
.
La documentazione di ConcurrentModificationException
dice:
Questa eccezione può essere generata da metodi che hanno rilevato modifiche simultanee di un oggetto quando tale modifica non è consentita ...
Si noti che questa eccezione non indica sempre che un oggetto è stato modificato contemporaneamente da un thread diverso. Se un singolo thread emette una sequenza di invocazioni di metodi che violano il contratto di un oggetto, l'oggetto può generare questa eccezione ...
Si noti che il comportamento fail-fast non può essere garantito in quanto, in generale, è impossibile fornire garanzie concrete in presenza di modifiche simultanee non sincronizzate. Operazioni fail-fast lanciate ConcurrentModificationException
sulla base del massimo sforzo.
Nota che
La documentazione del HashSet
, HashMap
, TreeSet
e ArrayList
le classi dice questo:
Gli iteratori restituiti [direttamente o indirettamente da questa classe] sono fail-fast: se la [raccolta] viene modificata in qualsiasi momento dopo la creazione dell'iteratore, in qualsiasi modo, tranne che attraverso il metodo di rimozione dell'iteratore stesso, i Iterator
lanci a ConcurrentModificationException
. Pertanto, di fronte a modifiche simultanee, l'iteratore fallisce in modo rapido e pulito, piuttosto che rischiare un comportamento arbitrario e non deterministico in un momento indeterminato in futuro.
Si noti che il comportamento fail-fast di un iteratore non può essere garantito in quanto, in generale, è impossibile fornire garanzie concrete in presenza di modifiche simultanee non sincronizzate. Gli iteratori fail-fast vengono lanciati ConcurrentModificationException
sulla base del massimo sforzo. Pertanto, sarebbe errato scrivere un programma che dipendesse da questa eccezione per la sua correttezza: il comportamento fail-fast degli iteratori dovrebbe essere usato solo per rilevare i bug .
Si noti ancora che il comportamento "non può essere garantito" ed è "solo sulla base del massimo sforzo".
La documentazione di diversi metodi Map
dell'interfaccia dice questo:
Le implementazioni non simultanee dovrebbero sovrascrivere questo metodo e, nel migliore dei modi, lanciare un ConcurrentModificationException
se viene rilevato che la funzione di mappatura modifica questa mappa durante il calcolo. Le implementazioni simultanee dovrebbero sovrascrivere questo metodo e, nel migliore dei modi, lanciare un IllegalStateException
se viene rilevato che la funzione di mappatura modifica questa mappa durante il calcolo e di conseguenza il calcolo non si completerebbe mai.
Notare di nuovo che per il rilevamento è necessaria solo una "base del massimo sforzo" e una ConcurrentModificationException
è esplicitamente suggerita solo per le classi non concorrenti (non thread-safe).
Debug ConcurrentModificationException
Quindi, quando vedi una traccia dello stack dovuta a ConcurrentModificationException
, non puoi immediatamente supporre che la causa sia un accesso multi-thread non sicuro a Collection
. È necessario esaminare lo stack-trace per determinare quale classe di Collection
ha generato l'eccezione (un metodo della classe l'avrà lanciata direttamente o indirettamente) e per quale Collection
oggetto. Quindi è necessario esaminare da dove l'oggetto può essere modificato.
- La causa più comune è la modifica di
Collection
all'interno di un for
loop avanzato rispetto a Collection
. Solo perché non vedi un Iterator
oggetto nel tuo codice sorgente non significa che non Iterator
ci sia! Fortunatamente, una delle affermazioni del for
loop difettoso di solito si trova nello stack-trace, quindi di solito è facile rintracciare l'errore.
- Un caso più complicato è quando il codice passa attorno ai riferimenti
Collection
all'oggetto. Si noti che le viste non modificabili delle raccolte (come quelle prodotte da Collections.unmodifiableList()
) conservano un riferimento alla raccolta modificabile, quindi l' iterazione su una raccolta "non modificabile" può generare l'eccezione (la modifica è stata apportata altrove). Altre vista della vostra Collection
, come ad esempio le liste sub , Map
set di entrata e Map
set di chiavi mantengono anche riferimenti a quello originale (modificabile) Collection
. Questo può essere un problema anche per un thread-safe Collection
, comeCopyOnWriteList
; non dare per scontato che le raccolte thread-safe (simultanee) non possano mai generare l'eccezione.
- Quali operazioni possono modificare a
Collection
possono essere inattese in alcuni casi. Per esempio,LinkedHashMap.get()
modifica la sua raccolta .
- I casi più difficili si verificano quando l'eccezione è dovuta a modifiche simultanee da parte di più thread.
Programmazione per prevenire errori di modifica simultanei
Quando possibile, limitare tutti i riferimenti a un Collection
oggetto, quindi è più facile prevenire modifiche simultanee. Crea Collection
un private
oggetto o una variabile locale e non restituire riferimenti ai Collection
suoi iteratori dai metodi. È quindi molto più semplice esaminare tutti i luoghi in cui è Collection
possibile modificarli. Se Collection
deve essere utilizzato da più thread, è quindi pratico assicurarsi che i thread accedano Collection
all'unico con sincronizzazione e blocco appropriati.