Почему 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 — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки