Назад к вопросам
Middle
101
questionbank

Как избежать исключения ConcurrentModificationException при переборе коллекции?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Для избежания ConcurrentModificationException при переборе коллекции можно использовать следующие подходы:

  1. Использовать потокобезопасные коллекции: java.util.concurrent предоставляет потокобезопасные альтернативы стандартным коллекциям. Например:

    • CopyOnWriteArrayList вместо ArrayList
    • CopyOnWriteArraySet вместо HashSet
    • ConcurrentHashMap вместо HashMap
    // Пример с CopyOnWriteArrayList
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.Iterator;
    
    public class ConcurrentCollectionExample {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Итератор не выбрасывает ConcurrentModificationException
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Изменение коллекции во время итерации безопасно
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Final list: " + list);
        }
    }
    
  2. Блокировать коллекцию: Синхронизировать доступ к коллекции с использованием synchronized или блокировок java.util.concurrent.locks.

    // Пример с синхронизированным блоком
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class SynchronizedCollectionExample {
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("A");
           list.add("B");
           list.add("C");
    
           synchronized (list) { // Блокируем коллекцию для доступа
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // Нельзя изменять коллекцию здесь, если другой поток может получить доступ
                   // через этот же synchronized блок
               }
           }
           // Изменение коллекции вне синхронизированного блока или другим потоком
           // может все еще вызвать ConcurrentModificationException при параллельной итерации
       }
    }
    
  3. Использовать итератор для удаления элементов: Если нужно удалить элементы во время перебора, используйте метод remove() самого итератора (если он поддерживается коллекцией).

    // Пример удаления с использованием Iterator.remove()
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class IteratorRemoveExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Удаление элемента через итератор безопасно
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Final list: " + list);
        }
    }
    
  4. Перебирать копию коллекции: Создать копию коллекции и перебирать ее. Изменения в исходной коллекции не повлияют на итерацию по копии.

    // Пример перебора копии
    import java.util.ArrayList;
    import java.util.List;
    
    public class CopyIterationExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Создаем копию для перебора
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Можем изменять оригинальную коллекцию во время перебора копии
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Final list: " + list);
        }
    }
    

Выбор подхода зависит от конкретных требований: нужно ли изменять коллекцию во время итерации, насколько важна производительность и работает ли код в многопоточной среде. CopyOnWriteArrayList/CopyOnWriteArraySet подходят, когда чтений значительно больше, чем записей. Блокировка подходит, когда изменения происходят реже, но могут быть более частыми, чем при использовании CopyOnWrite. Использование Iterator.remove() или перебор копии применимы, когда изменения происходят только в пределах одной итерации или потока.