Почему Collections.synchronizedList не спасает от ConcurrentModificationException

Вопрос проверяет, понимаете ли вы разницу между потокобезопасностью отдельной операции и потокобезопасностью логической последовательности операций.

Collections.synchronizedList оборачивает список так, что каждый отдельный метод — add, get, remove — синхронизирован. Но итерация — это не один метод, а последовательность вызовов hasNext() и next(). Между ними лок отпускается и захватывается снова.

Если другой поток вставит или удалит элемент между двумя итерациями, итератор всё равно увидит модификацию счётчика modCount и бросит исключение.

List list = Collections.synchronizedList( new ArrayList<>() );

synchronized (list) { for (String s : list) { System.out.println(s); } }

Единственный рабочий способ — вручную синхронизироваться на самом объекте списка во время всей итерации, как в примере выше. Обёртка защищает только сами методы, не сценарий их совместного использования.

Следом почти всегда спрашивают: а как решить эту же задачу без ручной синхронизации на итерации. Ответ — CopyOnWriteArrayList, если запись редкая, а чтение частое: итератор там работает со снапшотом массива на момент создания и никогда не бросает ConcurrentModificationException, зато каждая запись копирует весь массив.

Один метод потокобезопасен — не значит, что последовательность методов потокобезопасна.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки