Модифікація в Collectionтой час як перебір , що з Collectionвикористанням Iteratorце не дозволяються більшістю Collectionкласів. Бібліотека Java називає спробу змінити Collectionітерацію через неї "одночасною модифікацією". Це, на жаль, говорить про єдину можливу причину - одночасну модифікацію кількома потоками, але це не так. Використовуючи лише один потік, можна створити ітератор для Collection(використовуючи Collection.iterator()або посилений forцикл ), запустити ітерацію (використовуючи Iterator.next()або еквівалентно входячи в тіло розширеного forциклу), змінити Collection, а потім продовжити ітерацію.
Щоб допомогти програмістам, деякі реалізації цих Collectionкласів намагаються виявити помилкові одночасні модифікації та кинути а, ConcurrentModificationExceptionякщо вони виявлять його. Однак загалом неможливо та практично гарантувати виявлення всіх одночасних модифікацій. Таким чином, помилкове використання Collectionне завжди призводить до закидання ConcurrentModificationException.
Документація ConcurrentModificationExceptionговорить:
Цей виняток може бути викинуто методами, які виявили одночасну модифікацію об'єкта, коли така модифікація не допустима ...
Зауважте, що цей виняток не завжди вказує на те, що об'єкт одночасно був змінений іншим потоком. Якщо один потік видає послідовність викликів методу, що порушує контракт об'єкта, об'єкт може викинути цей виняток ...
Зауважте, що невдала поведінка не може бути гарантована, оскільки, взагалі кажучи, неможливо дати будь-які жорсткі гарантії за наявності несинхронізованих одночасних модифікацій. Невдалі операції кидаються ConcurrentModificationExceptionна основі найкращих зусиль.
Зауважте, що
Документація з HashSet, HashMap, TreeSetі ArrayListкласів каже , що це:
Ітератори, що повертаються [безпосередньо чи опосередковано з цього класу], є невдалими: якщо [колекція] модифікується в будь-який час після створення ітератора, будь-яким способом, крім методу власного видалення ітератора, Iteratorвикидання a ConcurrentModificationException. Таким чином, в умовах одночасних модифікацій ітератор виходить з ладу швидко і чисто, а не ризикує довільною недетермінованою поведінкою у невизначений час у майбутньому.
Зауважте, що невдала поведінка ітератора не може бути гарантована, оскільки це, як правило, неможливо дати будь-які жорсткі гарантії за наявності несинхронізованих одночасних модифікацій. Невдалі ітератори кидаються ConcurrentModificationExceptionна основі найкращих зусиль. Тому було б неправильно писати програму, яка залежала від цього винятку від її правильності: невдала поведінка ітераторів повинна використовуватися лише для виявлення помилок .
Зауважте ще раз, що поведінку "не можна гарантувати" та є лише "на основі найкращих зусиль".
Документація декількох методів Mapінтерфейсу говорить про це:
Несупутнє впровадження повинно змінити цей метод і, на основі найкращих зусиль, кинути а, ConcurrentModificationExceptionякщо виявлено, що функція відображення модифікує цю карту під час обчислення. Одночасні реалізації повинні замінити цей метод і, на основі найкращих зусиль, кинути, IllegalStateExceptionякщо виявлено, що функція відображення модифікує цю карту під час обчислень, і в результаті обчислення ніколи не завершиться.
Зауважте ще раз, що для виявлення потрібна лише "основа найкращих зусиль", а ConcurrentModificationExceptionявно пропонується лише для класів, які не є одночасними (не захищені від потоку).
Налагодження ConcurrentModificationException
Отже, побачивши трасування стека через a ConcurrentModificationException, ви не можете відразу припустити, що причиною є небезпечний багатопотоковий доступ до a Collection. Ви повинні вивчити трасування стека, щоб визначити, який клас Collectionвикинув виняток (метод класу буде прямо чи опосередковано кинутий його) та для якого Collectionоб'єкта. Потім ви повинні вивчити, звідки цей об’єкт можна змінити.
- Найчастішою причиною є модифікація в
Collectionмежах посиленого forциклу над Collection. Тільки те, що ви не бачите Iteratorоб’єкт у своєму вихідному коді, не означає, що його немає Iterator! На щастя, одне з висловлювань несправного forциклу, як правило, знаходиться у сліді стека, тому відстежувати помилку зазвичай легко.
- Більш складний випадок - коли ваш код обходить посилання на
Collectionоб'єкт. Зауважте, що немодифіковані представлення колекцій (таких як створені компанією Collections.unmodifiableList()) зберігають посилання на колекцію, що може змінюватися, тому ітерація над "немодифікованою" колекцією може викинути виняток (модифікація зроблена в іншому місці). Інші представлення ваших підрозділівCollection , такі як підсписки , Mapнабори записів та Mapнабори ключів, також зберігають посилання на оригінал (змінюються) Collection. Це може бути проблемою навіть для безпечного потоку Collection, наприклад CopyOnWriteList; не вважайте, що безпечні для потоків (одночасні) колекції ніколи не можуть кинути виняток.
- Які операції можуть модифікувати а,
Collectionможуть бути несподіваними в деяких випадках. Наприклад, LinkedHashMap.get()модифікує свою колекцію .
- Найважчі випадки, коли виняток відбувається через одночасну модифікацію декількома потоками.
Програмування для запобігання одночасних помилок модифікації
Коли це можливо, обмежте всі посилання на Collectionоб'єкт, тому його легше запобігти одночасним модифікаціям. Зробіть Collectionна privateоб'єкт або локальну змінну, а не повертати посилання на Collectionабо його ітератори з методів. Тоді набагато простіше оглянути всі місця, де Collectionможна змінити. Якщо використовується Collectionдля використання декількох потоків, то практичним є забезпечення того, щоб потоки Collectionмали доступ до єдиного з відповідною синхронізацією та блокуванням.