ConcurrentModificationException сообщает о некорректном изменении коллекции во время итерации независимо от числа потоков; поэтому однопоточная модификация с нарушением правил итерации — частая причина.
Может ли ConcurrentModificationException возникнуть в однопоточном коде и почему?
ConcurrentModificationException сообщает о некорректном изменении коллекции во время итерации независимо от числа потоков; поэтому однопоточная модификация с нарушением правил итерации — частая причина.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
ConcurrentModificationException в однопоточном коде: бывает ли такое?
- область: Java коллекции, исключения
- ConcurrentModificationException (CME) — исключение, возникающее, когда во время итерации обнаруживается параллельное изменение коллекции
- В однопоточном коде CME может появиться, если:
- одновременно применяются методы итератора и выполняется прямая модификация коллекции (например, remove/clear вне итератора)
- итератор вызывается после изменения коллекции другим методом без reset итератора
- CME не указывает однозначно на наличие нескольких потоков: его причина — структурное изменение коллекции во время итерации
- часто исключение возникает из-за вызова методов, меняющих коллекцию, не через итератор внутри цикла
- для предотвращения следует применять методы итератора при удалении/изменении либо итерироваться по копии коллекции
- практический пример:
for (Element e : list) { list.remove(e); }вызывает CME даже при выполнении в одном потоке
Итого: ConcurrentModificationException сообщает о некорректном изменении коллекции во время итерации независимо от числа потоков; поэтому однопоточная модификация с нарушением правил итерации — частая причина.
Подробный ответ
Основной ответ
ConcurrentModificationException (CME) — исключение, которое обычно появляется при структурном изменении коллекции во время её обхода с помощью fail-fast итератора в Java. Хотя в классическом представлении CME связывают с конкурентным доступом нескольких потоков, такое исключение возможно и в однопоточном коде. Например, цикл может перебирать коллекцию, а его тело — напрямую изменять её, не используя итератор.
Ключевые моменты
- Модификация в одном потоке во время итерации: если коллекция перебирается через
for-eachили явный итератор, а внутри цикла вызываются методы, меняющие её размер (add(),remove()), итератор обнаруживает несовпадение числа модификаций и выбрасывает CME. - Fail-fast итераторы: коллекции из
java.util, включаяArrayListиHashSet, используют fail-fast итераторы. Они контролируют число изменений коллекции с помощью поляmodCount. Если при вызовеnext()значение этой переменной не совпадает, возникает исключение. - Причина заключается не в многопоточности как таковой, а в структурном изменении коллекции во время обхода: даже один поток вызовет CME, если логика неосторожно изменяет коллекцию, например меняет список в цикле без применения метода итератора
remove().
Практический контекст
В реальных проектах CME нередко появляется из-за неправильной работы с коллекциями в однопоточном коде, особенно в сложных циклах, где коллекция изменяется во время обхода. Оптимальный вариант — использовать итератор и его методы либо создать копию коллекции для безопасной итерации. Для многопоточных сценариев применяют конкурентные коллекции из java.util.concurrent, например CopyOnWriteArrayList, либо синхронизацию.
Таким образом, ConcurrentModificationException возможен и в однопоточном коде: исключение указывает на некорректное изменение коллекции в процессе итерации, а не само по себе на параллельное выполнение.