Modyfikacja czasu Collectionpodczas iteracji przez to Collectionprzy użyciu an nieIterator jest dozwolona przez większość Collectionklas. Biblioteka Javy wywołuje próbę zmodyfikowania Collectionpodczas 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 forpętli ), rozpocząć iterację (używając Iterator.next()lub równoważnie wprowadzając treść rozszerzonej forpętli), zmodyfikować Collection, a następnie kontynuować iterację.
Aby pomóc programistom, niektóre implementacje tych Collectionklas próbują wykryć błędną, współbieżną modyfikację i wyrzucić, ConcurrentModificationExceptionjeśli ją wykryją. Jednak generalnie nie jest możliwe i praktyczne zagwarantowanie wykrywania wszystkich jednoczesnych modyfikacji. Tak więc błędne użycie Collectionnie zawsze kończy się rzutem ConcurrentModificationException.
Dokumentacja ConcurrentModificationExceptionmó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ą ConcurrentModificationExceptionnajlepszych starań.
Zwróć na to uwagę
Dokumentacja HashSet, HashMap, TreeSeti ArrayListzaję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, IteratorgenerujeConcurrentModificationException . 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, ConcurrentModificationExceptionco 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 Mapinterfejsu mówi tak:
Implementacje niewspółbieżne powinny przesłonić tę metodę i, na zasadzie najlepszego wysiłku, zgłosić a, ConcurrentModificationExceptionjeś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ć, IllegalStateExceptionjeś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 ConcurrentModificationExceptionjest 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 Collectionwyrzuciła wyjątek (metoda klasy bezpośrednio lub pośrednio go wyrzuci) i dla którego Collectionobiektu. Następnie musisz zbadać, skąd ten obiekt może być modyfikowany.
- Najczęstszą przyczyną jest modyfikacja
Collectionwewnątrz rozszerzonej forpętli na Collection. To, że nie widzisz Iteratorobiektu w kodzie źródłowym, nie oznacza, że go nie Iteratorma! Na szczęście jedno z instrukcji błędnej forpę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 , Mapzestawy wejścia i Mapnajważ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ć,
Collectionmoż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 Collectionobiektu, aby łatwiej było zapobiec równoczesnym modyfikacjom. Dokonać Collectiondo privateobiektu lub zmiennej lokalnej, a nie powrót do odwołania Collectionlub jego iteratorów z metod. Dużo łatwiej jest wtedy zbadać wszystkie miejsca, w których Collectionmożna zmodyfikować. Jeśli Collectionma być używany przez wiele wątków, praktyczne jest upewnienie się, że wątki mają dostęp Collectiontylko do tych z odpowiednią synchronizacją i blokowaniem.