Модификация коллекции Java в однопоточной и многопоточной среде
Published: 2023-12-11 11:59:27

Что будет результатом выполнения такой программы?

```java
    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            for (String el : list) {
                if (el.equals("b")) {
                    list.remove(el);
                }
            }
            System.out.println(list);
        }
    }
```

Эта программа бросит исключение ConcurrentModificationException. Почему так происходит? for-each цикл в Java использует Iterator. И если мы используем итератор, то удаление нужно делать при помощи итератора. Если же мы удаление делаем при помощи метода list.remove(el), то при вызове метода next будет брошено исключение ConcurrentModificationException:

```java
    Exception in thread "main" java.util.ConcurrentModificationException
     at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
     at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
```

Как это исправить?

Можно использовать Iterator явно и вызвать метод remove итератора:

```java
    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            Iterator iterator = list.iterator();
            while (iterator.hasNext()) {
                String el = (String) iterator.next();
                if (el.equals("b")) {
                    iterator.remove();
                }
            }
            System.out.println(list);
        }
    }
```

Результат:

```java
    [a, c, d]
```

Или же можно использовать removeIf:

```java
    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.removeIf(el -> el.equals("b"));
            System.out.println(list);
        }
    }
```

Или не использовать итератор вообще:

```java
    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            for (int i = 0; i < list.size(); i++) {
                String el = list.get(i);
                if (el.equals("b")) {
                    list.remove(el);
                }
            }
            System.out.println(list);
        }
    }
```

## Многопоточная среда:

Допустим у нас в одном потоке есть итерирование по коллекции, а во втором потоке у нас происходит модификация этой коллекции (удаление или добавление элемента). Чтобы предотвратить ConcurrentModificationException можно использовать:

 1. synchronized блок перед итерированием:

 2. Использовать потокобезопасные коллекции: ConcurrentHashMap, CopyOnWriteArrayList

Посмотрим вариант с synchonized блоком:

```java
    Первый поток:
    synchronized (list) {
        for (String el : list) {
            System.out.println(el);
        }
    }
    Второй поток:
    synchronized (list) {
        list.remove("b");
    }
```

Можно ли избежать synchronized блока, если использовать:

```java
    Collections.synchronizedList(list);
```

Ответ: **нет**. В таком случае может быть также брошен ConcurrentModificationException. В таком случае также нужно помещать в synchonized блок.

Если мы используем **ConcurrentHashMap**, **CopyOnWriteArrayList** то можно итерироваться по коллекции в одном потоке и модифицировать ее в другом **без synchonized блока** не опасаясь ConcurrentModificationException.