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

Сталкивался ли с ConcurrentModificationException

2.0 Middle🔥 141 комментариев
#Другое

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

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

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

ConcurrentModificationException в Java

Да, я неоднократно сталкивался с этим исключением, особенно на ранних этапах разработки многопоточного кода. Это одна из самых частых ошибок при работе с коллекциями.

Что это такое?

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

Типичные сценарии

Сценарий 1: Удаление элемента во время итерации

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

// ❌ Вызывает ConcurrentModificationException
for (String name : names) {
    if (name.equals("Bob")) {
        names.remove(name);  // Изменение списка во время итерации
    }
}

Решение 1: Использование Iterator

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

// ✅ Правильный способ
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
    String name = iterator.next();
    if (name.equals("Bob")) {
        iterator.remove();  // Безопасное удаление
    }
}

Решение 2: Создание нового списка

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

// ✅ Удаляем элементы через фильтрацию
names = names.stream()
    .filter(name -> !name.equals("Bob"))
    .collect(Collectors.toList());

Решение 3: Копирование и итерация по копии

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

// ✅ Итерируем по копии, модифицируем оригинал
for (String name : new ArrayList<>(names)) {
    if (name.equals("Bob")) {
        names.remove(name);
    }
}

Сценарий 2: Многопоточная модификация

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

// ❌ Опасно в многопоточной среде
ExecutorService executor = Executors.newFixedThreadPool(2);

executor.submit(() -> {
    for (String name : names) {
        System.out.println(name);
    }
});

executor.submit(() -> {
    names.remove(0);  // Другой поток изменяет список
});

Решение: Использование потокобезопасных коллекций

// ✅ CopyOnWriteArrayList
List<String> names = new CopyOnWriteArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));

ExecutorService executor = Executors.newFixedThreadPool(2);

executor.submit(() -> {
    for (String name : names) {
        System.out.println(name);
    }
});

executor.submit(() -> {
    names.remove(0);  // Безопасно
});

Практический пример: Обработка событий

public class EventManager {
    private List<EventListener> listeners = new CopyOnWriteArrayList<>();
    
    public void subscribe(EventListener listener) {
        listeners.add(listener);
    }
    
    public void unsubscribe(EventListener listener) {
        listeners.remove(listener);
    }
    
    public void notify(Event event) {
        // ✅ Безопасно — CopyOnWriteArrayList позволяет модифицировать 
        // список во время итерации
        for (EventListener listener : listeners) {
            listener.onEvent(event);
        }
    }
}

Ключевые методы для работы

  • Iterator.remove() — удаляет последний элемент, полученный next()
  • List.removeIf() — удаляет элементы, соответствующие условию
  • Stream API — создаёт новую коллекцию без модификации исходной
  • CopyOnWriteArrayList — для частых читаний и редких записей в многопоточной среде
  • Collections.synchronizedList() — синхронизированный список (для read-write операций)

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

  1. Никогда не изменяйте коллекцию во время итерации — используйте Iterator
  2. Используйте правильные структуры для многопоточности — CopyOnWriteArrayList, ConcurrentHashMap
  3. Воспользуйтесь Stream API — это более функциональный подход
  4. Думайте о синхронизации — если разные потоки читают и пишут, нужна защита
  5. Тестируйте многопоточный код — используйте инструменты для проверки race conditions

Заключение

ConcurrentModificationException — это защитный механизм Java, который предупреждает об ошибке в логике кода. Правильное использование Iterator, Stream API и потокобезопасных коллекций позволяет избежать этой проблемы и написать надёжный код.

Сталкивался ли с ConcurrentModificationException | PrepBro