Modyfikacja czasu Collection
podczas iteracji przez to Collection
przy użyciu an nieIterator
jest dozwolona przez większość Collection
klas. Biblioteka Javy wywołuje próbę zmodyfikowania Collection
podczas iteracji przez nią „współbieżną modyfikacją”. To niestety sugeruje, że jedyną możliwą przyczyną jest jednoczesna modyfikacja przez wiele wątków, ale tak nie jest. Używając tylko jednego wątku, można utworzyć iterator dla Collection
(używając Collection.iterator()
lub rozszerzonej for
pętli ), rozpocząć iterację (używając Iterator.next()
lub równoważnie wprowadzając treść rozszerzonej for
pętli), zmodyfikować Collection
, a następnie kontynuować iterację.
Aby pomóc programistom, niektóre implementacje tych Collection
klas próbują wykryć błędną, współbieżną modyfikację i wyrzucić, ConcurrentModificationException
jeśli ją wykryją. Jednak generalnie nie jest możliwe i praktyczne zagwarantowanie wykrywania wszystkich jednoczesnych modyfikacji. Tak więc błędne użycie Collection
nie zawsze kończy się rzutem ConcurrentModificationException
.
Dokumentacja ConcurrentModificationException
mówi:
Ten wyjątek może zostać zgłoszony przez metody, które wykryły równoczesną modyfikację obiektu, gdy taka modyfikacja jest niedozwolona ...
Należy zauważyć, że ten wyjątek nie zawsze oznacza, że obiekt został jednocześnie zmodyfikowany przez inny wątek. Jeśli pojedynczy wątek wydaje sekwencję wywołań metod, która narusza kontrakt obiektu, obiekt może zgłosić ten wyjątek ...
Należy zauważyć, że nie można zagwarantować zachowania bezawaryjnego, ponieważ, ogólnie rzecz biorąc, niemożliwe jest zapewnienie jakichkolwiek twardych gwarancji w przypadku niezsynchronizowanej, równoczesnej modyfikacji. Operacje bezawaryjne wymagają ConcurrentModificationException
najlepszych starań.
Zwróć na to uwagę
Dokumentacja HashSet
, HashMap
, TreeSet
i ArrayList
zajęcia mówi w ten sposób:
Iteratory zwrócone [bezpośrednio lub pośrednio z tej klasy] działają bezawaryjnie: jeśli [kolekcja] zostanie zmodyfikowana w dowolnym momencie po utworzeniu iteratora, w jakikolwiek sposób z wyjątkiem metody usuwania iteratora, Iterator
generujeConcurrentModificationException
. Zatem w obliczu równoczesnej modyfikacji iterator zawodzi szybko i czysto, zamiast ryzykować arbitralne, niedeterministyczne zachowanie w nieokreślonym czasie w przyszłości.
Należy zauważyć, że nie można zagwarantować szybkiego zachowania iteratora w przypadku awarii, ponieważ, ogólnie rzecz biorąc, niemożliwe jest zapewnienie jakichkolwiek twardych gwarancji w przypadku niezsynchronizowanej współbieżnej modyfikacji. Iteratory działające bezawaryjnie wykonują wszystko, ConcurrentModificationException
co najlepsze. Dlatego błędem byłoby napisanie programu, który zależałby od tego wyjątku ze względu na jego poprawność: szybkie zachowanie iteratorów w przypadku awarii powinno być używane tylko do wykrywania błędów .
Zauważ jeszcze raz, że zachowanie „nie może być zagwarantowane” i jest tylko „na zasadzie najlepszych starań”.
Dokumentacja kilku metod Map
interfejsu mówi tak:
Implementacje niewspółbieżne powinny przesłonić tę metodę i, na zasadzie najlepszego wysiłku, zgłosić a, ConcurrentModificationException
jeśli zostanie wykryte, że funkcja mapowania modyfikuje tę mapę podczas obliczeń. Współbieżne implementacje powinny przesłonić tę metodę i, na zasadzie najlepszego wysiłku, zgłosić, IllegalStateException
jeśli zostanie wykryte, że funkcja mapowania modyfikuje tę mapę podczas obliczeń, w wyniku czego obliczenia nigdy się nie zakończą.
Zwróć uwagę, że do wykrywania wymagana jest tylko „podstawa najlepszego wysiłku”, a a ConcurrentModificationException
jest wyraźnie sugerowane tylko dla klas innych niż współbieżne (niegwintowane).
Debugowanie ConcurrentModificationException
Tak więc, gdy widzisz ślad stosu związany z a ConcurrentModificationException
, nie możesz od razu założyć, że przyczyną jest niebezpieczny wielowątkowy dostęp do pliku Collection
. Musisz zbadać ślad stosu, aby określić, która klasa Collection
wyrzuciła wyjątek (metoda klasy bezpośrednio lub pośrednio go wyrzuci) i dla którego Collection
obiektu. Następnie musisz zbadać, skąd ten obiekt może być modyfikowany.
- Najczęstszą przyczyną jest modyfikacja
Collection
wewnątrz rozszerzonej for
pętli na Collection
. To, że nie widzisz Iterator
obiektu w kodzie źródłowym, nie oznacza, że go nie Iterator
ma! Na szczęście jedno z instrukcji błędnej for
pętli będzie zwykle znajdować się w śladzie stosu, więc śledzenie błędu jest zwykle łatwe.
- Trudniejszy przypadek ma miejsce, gdy kod przekazuje odwołania do
Collection
obiektu. Należy zauważyć, że niemodyfikowalne widoki kolekcji (na przykład utworzone przez Collections.unmodifiableList()
) zachowują odniesienie do modyfikowalnej kolekcji, więc iteracja po kolekcji „niemodyfikowalnej” może zgłosić wyjątek (modyfikacja została przeprowadzona gdzie indziej). Inne poglądy twoich Collection
, takie jak list sub , Map
zestawy wejścia i Map
najważniejszych zbiorów zachowują również odniesienia do oryginału (modyfikacji) Collection
. Może to stanowić problem nawet dla bezpiecznego wątku Collection
, takiego jak CopyOnWriteList
; Nie zakładaj, że kolekcje bezpieczne wątkowo (współbieżne) nigdy nie mogą zgłosić wyjątku.
- To, które operacje mogą modyfikować,
Collection
może być w niektórych przypadkach nieoczekiwane. Na przykład LinkedHashMap.get()
modyfikuje swoją kolekcję .
- Najtrudniejsze przypadki, gdy wyjątek jest w wyniku jednoczesnej modyfikacji przez wielu wątków.
Programowanie, aby zapobiec jednoczesnym błędom modyfikacji
Jeśli to możliwe, ogranicz wszystkie odwołania do Collection
obiektu, aby łatwiej było zapobiec równoczesnym modyfikacjom. Dokonać Collection
do private
obiektu lub zmiennej lokalnej, a nie powrót do odwołania Collection
lub jego iteratorów z metod. Dużo łatwiej jest wtedy zbadać wszystkie miejsca, w których Collection
można zmodyfikować. Jeśli Collection
ma być używany przez wiele wątków, praktyczne jest upewnienie się, że wątki mają dostęp Collection
tylko do tych z odpowiednią synchronizacją i blokowaniem.