Модифікація в 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
мали доступ до єдиного з відповідною синхронізацією та блокуванням.