Модификация Collection
при повторении этого Collection
использование Iterator
запрещено большинством Collection
классов. Библиотека Java называет попытку изменить Collection
во время его итерации как одновременную модификацию. К сожалению, это говорит о том, что единственная возможная причина - одновременное изменение несколькими потоками, но это не так. Используя только один поток, можно создать итератор для Collection
(используя _ 7_ или расширенный for
loop), начните итерацию (используя _ 9_, или, что эквивалентно, введя тело расширенного for
цикла), измените Collection
, затем продолжите итерацию.
Чтобы помочь программистам, некоторые реализации этих Collection
классов пытаются обнаруживать ошибочные одновременные модификации и выдают ConcurrentModificationException
, если обнаруживают это. Однако, как правило, невозможно и практически невозможно гарантировать обнаружение всех одновременных модификаций. Таким образом, ошибочное использование Collection
не всегда приводит к выбросу ConcurrentModificationException
.
В документации ConcurrentModificationException
говорится:
Это исключение может быть вызвано методами, обнаружившими одновременную модификацию объекта, когда такая модификация недопустима ...
Обратите внимание, что это исключение не всегда означает, что объект был одновременно изменен другим потоком. Если один поток выдает последовательность вызовов метода, которая нарушает контракт объекта, объект может сгенерировать это исключение ...
Обратите внимание, что отказоустойчивое поведение не может быть гарантировано, поскольку, вообще говоря, невозможно дать какие-либо жесткие гарантии при наличии несинхронизированной одновременной модификации. Безотказные операции бросают ConcurrentModificationException
максимальные усилия.
Обратите внимание, что
Документация HashSet
, _ 19_, _ 20_ и _ 21_ классы говорят следующее:
Итераторы, возвращаемые [прямо или косвенно из этого класса], работают без сбоев: если [коллекция] изменяется в любое время после создания итератора, любым способом, кроме собственного метода удаления итератора, Iterator
выдает ConcurrentModificationException
. Таким образом, перед лицом одновременной модификации итератор быстро и чисто выходит из строя, вместо того, чтобы рисковать произвольным, недетерминированным поведением в неопределенное время в будущем.
Обратите внимание, что безотказное поведение итератора не может быть гарантировано, поскольку, вообще говоря, невозможно дать какие-либо жесткие гарантии при наличии несинхронизированной одновременной модификации. Отказоустойчивые итераторы бросают ConcurrentModificationException
по принципу «максимальных усилий». Следовательно, было бы неправильно писать программу, правильность которой зависела бы от этого исключения: отказоустойчивое поведение итераторов следует использовать только для обнаружения ошибок.
Еще раз обратите внимание, что такое поведение не может быть гарантировано и требует максимальных усилий.
Документация по нескольким методам интерфейса Map
. скажи это:
Непараллельные реализации должны переопределять этот метод и из соображений максимальной эффективности выдавать ConcurrentModificationException
, если обнаруживается, что функция сопоставления изменяет эту карту во время вычислений. Параллельные реализации должны переопределять этот метод и из соображений максимальной эффективности выдавать IllegalStateException
, если обнаруживается, что функция сопоставления изменяет эту карту во время вычисления, и в результате вычисление никогда не будет завершено.
Еще раз обратите внимание, что для обнаружения требуется только основа максимальных усилий, а ConcurrentModificationException
явно предлагается только для не параллельных (небезопасных для потоков) классов.
Отладка ConcurrentModificationException
Итак, когда вы видите трассировку стека из-за ConcurrentModificationException
, вы не можете сразу предположить, что причиной является небезопасный многопоточный доступ к Collection
. Вы должны изучите трассировку стека, чтобы определить, какой класс Collection
вызвал исключение (метод этого класса прямо или косвенно вызовет его) и для какого Collection
объекта. Затем вы должны изучить, откуда этот объект может быть изменен.
- Наиболее частая причина - модификация
Collection
в расширенном for
цикле вместо Collection
. Тот факт, что вы не видите Iterator
объект в исходном коде, не означает, что его там нет Iterator
! К счастью, один из операторов неисправного цикла for
обычно находится в трассировке стека, поэтому отследить ошибку обычно несложно.
- Более сложный случай - это когда ваш код передает ссылки на объект
Collection
. Обратите внимание, что неизменяемые представления коллекций (например, созданные _ 41_) сохранить ссылку на изменяемую коллекцию, поэтому итерация по неизменяемой коллекции может вызвать исключение (изменение было выполнено в другом месте). Другие представления вашего Collection
, например подсписки, _ 43_ набора записей и _ 44_ наборы ключей также сохраняют ссылки на исходный (изменяемый) Collection
. Это может быть проблемой даже для поточно-ориентированного Collection
, такого как CopyOnWriteList
; не предполагайте, что потокобезопасные (параллельные) коллекции никогда не могут вызвать исключение.
- Какие операции могут изменить
Collection
, в некоторых случаях может быть неожиданным. Например, LinkedHashMap.get()
изменяет свою коллекцию.
- Самые сложные случаи - это когда исключение связано с одновременной модификацией несколькими потоками.
Программирование для предотвращения ошибок одновременной модификации
По возможности ограничивайте все ссылки объектом Collection
, чтобы было легче предотвратить одновременные изменения. Сделайте Collection
объектом private
или локальной переменной и не возвращайте ссылки на Collection
или его итераторы из методов. Тогда будет намного проще изучить все места, где Collection
может быть изменен. Если Collection
должен использоваться несколькими потоками, тогда целесообразно гарантировать, что потоки обращаются к Collection
только с соответствующей синхронизацией и блокировкой.
person
Raedwald
schedule
13.03.2019