← Назад к вопросам

В чем причина получения ConcurrentModificationException в ArrayList

2.2 Middle🔥 221 комментариев
#Коллекции#Многопоточность#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

ConcurrentModificationException в ArrayList

Это классическая ошибка при работе с коллекциями. ConcurrentModificationException выбрасывается, когда структура коллекции изменяется во время итерации по ней.

Основная причина

ArrayList хранит внутренний счётчик modCount, который увеличивается при любом изменении структуры коллекции (add, remove, clear). Во время итерации Iterator проверяет, не изменился ли modCount. Если изменился - выбрасывается исключение.

Самая распространённая ошибка

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

// ОШИБКА - ConcurrentModificationException
for (String item : list) {
    if (item.equals("b")) {
        list.remove(item); // Изменяем структуру!
    }
}

Почему происходит исключение:

  • for-each цикл использует Iterator
  • list.remove() увеличивает modCount
  • Iterator видит, что modCount изменился
  • Выбросить ConcurrentModificationException

Удаление через iterator.remove() - правильно

// ПРАВИЛЬНО - не будет исключения
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.equals("b")) {
        it.remove(); // Это безопасно!
    }
}

// Результат: ["a", "c"]

Почему это работает:

  • Iterator.remove() корректно обновляет свой внутренний счётчик
  • modCount и expectedModCount остаются синхронизированы

Многопоточность - реальная конкуренция

// ОШИБКА - реальная конкуренция
ArrayList<String> list = new ArrayList<>();

// Thread 1
for (String item : list) {
    System.out.println(item);
}

// Thread 2 (одновременно)
list.add("new item"); // ConcurrentModificationException в Thread 1!

Решение - использовать CopyOnWriteArrayList:

// ПРАВИЛЬНО - потокобезопасно
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

// Thread 1
for (String item : list) {
    System.out.println(item); // Безопасно
}

// Thread 2
list.add("new item"); // Не вызовет исключение

Практические решения

Решение 1: Использовать Iterator.remove()

ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.contains("a")) {
        it.remove(); // Безопасно
    }
}

Решение 2: Создать новый список

ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

ArrayList<String> filtered = new ArrayList<>();
for (String item : list) {
    if (!item.equals("banana")) {
        filtered.add(item);
    }
}

list = filtered;

Решение 3: Использовать removeIf() - ЛУЧШИЙ СПОСОБ

ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

list.removeIf(item -> item.equals("banana"));
// Результат: ["apple", "cherry"]

Это самый безопасный и читаемый способ!

Решение 4: Stream API

ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

list = list.stream()
    .filter(item -> !item.equals("banana"))
    .collect(Collectors.toCollection(ArrayList::new));

Внутреннее устройство ArrayList

private int modCount = 0; // Счётчик изменений

public boolean add(E e) {
    modCount++; // Увеличить счётчик
    // ...
}

// Iterator проверяет modCount
private class Itr implements Iterator<E> {
    int expectedModCount = modCount;
    
    public E next() {
        checkForComodification();
        // ...
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

Таблица безопасных операций

ОперацияБезопасна?Почему
list.remove() в for-eachНетМеняет modCount
iterator.remove()ДаСинхронизирует счётчики
list.removeIf()ДаВстроенный механизм
Stream.filter()ДаНовый список

Лучшие практики

// Плохо
for (Item item : list) {
    if (shouldRemove(item)) {
        list.remove(item);
    }
}

// Хорошо - используй removeIf
list.removeIf(item -> shouldRemove(item));

// Хорошо - используй iterator
Iterator<Item> it = list.iterator();
while (it.hasNext()) {
    if (shouldRemove(it.next())) {
        it.remove();
    }
}

Заключение

ConcurrentModificationException выбрасывается, когда ArrayList изменяется во время итерации, потому что Iterator видит несоответствие между expectedModCount и текущим modCount.

Используй:

  • removeIf() - для простых случаев
  • Iterator.remove() - когда нужен контроль
  • Stream API - для функционального стиля
  • Избегай прямого удаления через list.remove() в циклах
В чем причина получения ConcurrentModificationException в ArrayList | PrepBro