La modifica di un Collectionpo 'che scorre attraverso l' Collectionuso di un nonIterator è consentita dalla maggior parte delle Collectionclassi. La libreria Java chiama un tentativo di modifica Collectionmentre 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 forciclo migliorato ), modificare Collection, quindi continuare iterando.
Per aiutare i programmatori, alcune implementazioni di quelle Collectionclassi tentano di rilevare modifiche simultanee errate e lanciano un ConcurrentModificationExceptionse la rilevano. Tuttavia, in generale non è possibile e pratico garantire il rilevamento di tutte le modifiche simultanee. Quindi un uso errato del Collectionnon sempre provoca un lancio ConcurrentModificationException.
La documentazione di ConcurrentModificationExceptiondice:
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 ConcurrentModificationExceptionsulla base del massimo sforzo.
Nota che
La documentazione del HashSet, HashMap, TreeSete ArrayListle 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 Iteratorlanci 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 ConcurrentModificationExceptionsulla 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 Mapdell'interfaccia dice questo:
Le implementazioni non simultanee dovrebbero sovrascrivere questo metodo e, nel migliore dei modi, lanciare un ConcurrentModificationExceptionse 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 IllegalStateExceptionse 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 Collectionha generato l'eccezione (un metodo della classe l'avrà lanciata direttamente o indirettamente) e per quale Collectionoggetto. Quindi è necessario esaminare da dove l'oggetto può essere modificato.
- La causa più comune è la modifica di
Collectionall'interno di un forloop avanzato rispetto a Collection. Solo perché non vedi un Iteratoroggetto nel tuo codice sorgente non significa che non Iteratorci sia! Fortunatamente, una delle affermazioni del forloop 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
Collectionall'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 , Mapset di entrata e Mapset 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
Collectionpossono 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 Collectionoggetto, quindi è più facile prevenire modifiche simultanee. Crea Collectionun privateoggetto o una variabile locale e non restituire riferimenti ai Collectionsuoi iteratori dai metodi. È quindi molto più semplice esaminare tutti i luoghi in cui è Collectionpossibile modificarli. Se Collectiondeve essere utilizzato da più thread, è quindi pratico assicurarsi che i thread accedano Collectionall'unico con sincronizzazione e blocco appropriati.