- Home »

Исключение ConcurrentModificationException в Java — причины и решения
Если ты когда-нибудь писал серверные приложения на Java, особенно те, что крутятся на твоём любимом VPS или выделенном сервере, то наверняка сталкивался с загадочным и раздражающим ConcurrentModificationException. Эта статья — твой быстрый гайд по выживанию: что это за зверь, почему он появляется, как его приручить и не дать уронить твой сервис в самый неподходящий момент. Будет много практики, примеры из жизни, схемы, лайфхаки и даже немного гиковского юмора. Погнали!
ConcurrentModificationException: что это и почему это важно?
В двух словах: ConcurrentModificationException — это исключение, которое выбрасывается, когда ты пытаешься изменить коллекцию (например, ArrayList
, HashMap
и т.д.) во время её обхода (итерации) не тем способом, который ожидает Java. Это не баг, а фича: JVM специально защищает тебя от неожиданных и трудноуловимых ошибок, которые могут возникнуть при одновременной модификации коллекций несколькими потоками или даже одним, но неаккуратным способом.
Почему это важно? Потому что такие ошибки могут привести к падению твоего сервиса, потере данных или, что ещё хуже, к трудноуловимым багам, которые проявляются только под нагрузкой. А если у тебя на сервере крутится что-то критичное (например, биллинг, чат, или мониторинг), то такие фокусы могут стоить дорого.
Как это работает?
- Коллекции в Java (например,
ArrayList
,HashSet
) используют внутренний счётчик изменений —modCount
. - Когда ты создаёшь итератор (
Iterator
), он запоминает текущее значениеmodCount
. - Если во время обхода коллекции (
for-each
,iterator.next()
) кто-то меняет коллекцию напрямую (например,list.remove()
),modCount
увеличивается. - Итератор замечает несоответствие и выбрасывает
ConcurrentModificationException
.
Это сделано для того, чтобы ты не получил неожиданные результаты: например, не пропустил элемент или не получил NullPointerException
из-за того, что элемент уже удалён.
Как быстро и просто всё настроить?
Вот несколько практических советов, как избежать ConcurrentModificationException
и не тратить часы на отладку:
-
Используй правильные методы для удаления элементов во время итерации.
Вместоlist.remove()
используйiterator.remove()
:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("foo")) {
it.remove(); // ОК!
}
}
-
Не модифицируй коллекцию в другом потоке без синхронизации.
Если у тебя несколько потоков, используй потокобезопасные коллекции (CopyOnWriteArrayList
,ConcurrentHashMap
и т.д.). -
Для массовых изменений — собирай элементы в отдельный список, а потом удаляй.
List<String> toRemove = new ArrayList<>();
for (String s : list) {
if (s.startsWith("bad")) {
toRemove.add(s);
}
}
list.removeAll(toRemove);
-
Используй стримы (Streams) для фильтрации.
list = list.stream()
.filter(s -> !s.equals("foo"))
.collect(Collectors.toList());
Примеры, схемы, практические советы
Сценарий | Что происходит | Результат | Рекомендация |
---|---|---|---|
Удаление элемента через list.remove() внутри for-each |
Изменяется modCount коллекции |
ConcurrentModificationException |
Используй iterator.remove() |
Удаление через iterator.remove() |
Итератор знает об изменениях | Всё работает корректно | Рекомендуется |
Модификация коллекции в другом потоке | Несогласованное состояние коллекции | ConcurrentModificationException или баги |
Используй ConcurrentHashMap , CopyOnWriteArrayList |
Удаление элементов после обхода | Сначала собираешь, потом удаляешь | Всё ок, но медленнее | Для больших коллекций — аккуратно с памятью |
Положительные и отрицательные кейсы
-
Положительный: Используешь
ConcurrentHashMap
для хранения сессий пользователей на сервере. Можно безопасно удалять и добавлять элементы из разных потоков — никакогоConcurrentModificationException
! -
Отрицательный: В логике обработки очереди сообщений (
ArrayList
) в одном потоке идёт обход, а в другом — добавление новых сообщений. Итог — периодические падения сервиса сConcurrentModificationException
, особенно под нагрузкой.
Команды и сниппеты для быстрой настройки
Вот несколько полезных кусков кода, которые можно сразу вставлять в свой проект:
// Безопасное удаление элементов во время итерации
Iterator<User> it = users.iterator();
while (it.hasNext()) {
User user = it.next();
if (user.isInactive()) {
it.remove();
}
}
// Использование потокобезопасной коллекции
List<String> safeList = new CopyOnWriteArrayList<>(originalList);
// Фильтрация с помощью Stream API
list = list.stream()
.filter(item -> !item.isExpired())
.collect(Collectors.toList());
Похожие решения, программы и утилиты
- java.util.concurrent — стандартный пакет потокобезопасных коллекций.
- Guava Collections — расширенные коллекции от Google, иногда удобнее стандартных.
- Project Lombok — не совсем про коллекции, но ускоряет написание кода (например, генерация
equals
,hashCode
).
Статистика и сравнение с другими решениями
Коллекция | Потокобезопасность | Производительность | Где использовать |
---|---|---|---|
ArrayList | Нет | Высокая (однопоточно) | Локальные задачи, без многопоточности |
CopyOnWriteArrayList | Да | Средняя (чтение быстро, запись медленно) | Чтение чаще, чем запись (например, кэш) |
ConcurrentHashMap | Да | Высокая (многопоточно) | Сессии, кэш, глобальные мапы |
Vector | Да (устаревший способ) | Медленно | Не рекомендуется |
Интересные факты и нестандартные способы использования
- CopyOnWriteArrayList отлично подходит для хранения подписчиков на события в серверных приложениях: подписчиков много, читают часто, а добавляют/удаляют редко.
- ConcurrentModificationException — это unchecked exception, его можно не ловить, но лучше не допускать его появления вообще.
- Скрытая угроза: Даже если у тебя нет явной многопоточности, фреймворки (например, Spring, Tomcat) могут запускать твой код в разных потоках. Проверяй!
- Для автоматизации: Потокобезопасные коллекции позволяют писать скрипты для мониторинга и обслуживания серверов, которые не падают при параллельном доступе к данным.
Какие новые возможности открываются?
- Горизонтальное масштабирование: Безопасные коллекции позволяют легко масштабировать сервисы на несколько потоков и даже серверов.
- Автоматизация: Можно писать скрипты для обслуживания серверов (например, чистка неактивных сессий), не боясь, что что-то упадёт из-за гонки потоков.
- Интеграция с современными фреймворками: Большинство современных Java-фреймворков (Spring, Vert.x, Quarkus) используют потокобезопасные коллекции под капотом — твой код будет совместим.
Выводы и рекомендации
ConcurrentModificationException — это не баг, а твой друг, который предупреждает о потенциальных проблемах с коллекциями. Не игнорируй его! Используй правильные инструменты:
- Для однопоточных задач —
ArrayList
,HashMap
иiterator.remove()
. - Для многопоточных —
CopyOnWriteArrayList
,ConcurrentHashMap
и другие из java.util.concurrent. - Для массовых изменений — сначала собирай, потом удаляй.
- Для фильтрации — используй Stream API.
Если ты хочешь, чтобы твой сервер работал стабильно, не падал под нагрузкой и не терял данные — следуй этим советам. А если нужен надёжный VPS или выделенный сервер для своих Java-проектов — ты знаешь, где искать 😉
Пиши в комментариях свои кейсы, лайфхаки и вопросы — разберём вместе!
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.