Paolinos ha scritto:
non mi è chiaro il significato di ConcurrentModificationException e come va inserita nell'implementazione.
Prendiamo la classica iterazione:
Iterator<Xyz> iter = unaCollezione.iterator();
while (iter.hasNext()) {
Xyz xyz = iter.next();
// ......
}
Dal momento in cui si ottiene l'iteratore al momento in cui si arriva all'ultimo elemento può passare del tempo. Dipende ovviamente dal numero di elementi e da cosa fa il codice nel ciclo.
Se in questo frangente di tempo si va a modificare strutturalmente la collezione (add, set, remove, ecc...), sia all'interno del ciclo stesso, sia da parte ad esempio di un altro thread che ha visibilità della collezione, COSA dovrebbe succedere? O per meglio dire: come dovrebbe comportarsi l'iteratore da quel momento in avanti?
L'iteratore può continuare a iterare con successo? Può rischiare di "perdersi" degli elementi? Può causare altri problemi o danni alla struttura della collezione? La risposta è: DIPENDE da quale è il concetto della collezione e da come è implementata esattamente.
Le collezioni "base" del framework, quelle direttamente in java.util es. ArrayList, Vector, HashMap forniscono un iteratore che ha un comportamento che si dice "fail-fast". Se durante la iterazione si modifica strutturalmente la collezione in qualunque modo ECCETTO attraverso il remove() del Iterator, allora l'iteratore lancia ConcurrentModificationException.
Come si può fare questo? La implementazione di Oracle è abbastanza semplice: queste collezioni hanno un campo che fa da "contatore" delle modifiche (nei sorgenti Oracle si chiama
modCount). Ad ogni modifica, con add, set, remove, ecc... questo contatore viene semplicemente incrementato.
Quando vai ad ottenere l'Iterator, inizialmente si copia il valore del modCount in suo campo interno. Ogni volta che sul Iterator fai un next() o remove(), l'iteratore va a verificare se il suo contatore è ancora uguale a quello della collezione. Se non lo è lancia ConcurrentModificationException.
Il senso del ConcurrentModificationException è quindi solamente quello di segnalare che la collezione è stata modificata durante la iterazione e quindi la iterazione in teoria non dovrebbe più continuare perché ci potrebbero essere problemi.
Il termine "Concurrent" nel nome della eccezione non deve far pensare per forza al multi-threading. La collezione può essere modificata anche dentro quel ciclo while, quindi nello stesso thread che sta iterando!
Il comportamento fail-fast comunque non è garantito al 100%. Per un motivo molto semplice: per evitare di aggiungere overhead, quindi diminuire le prestazioni, quel modCount viene incrementato senza usare alcun meccanismo di sincronizzazione. Questo ha una implicazione importante: se un thread A sta iterando e un thread B modifica la collezione, il thread A potrebbe anche non "vedere" (subito o mai) la modifica del modCount e quindi non lancerebbe ConcurrentModificationException.
Alla fin fine, come dice la documentazione:
ConcurrentModificationException should be used only to detect bugs.
Ovvero se sbuca fuori ConcurrentModificationException dovrebbe denotare un baco poiché il codice non è stato fatto correttamente perché il programmatore ha permesso volutamente o per svista che la collezione venga modificata durante la iterazione.
Valuta tu se in questa tua esercitazione è importante o no implementare un comportamento fail-fast nell'iteratore.