Модификация коллекции Java в однопоточной и многопоточной среде

faangmaster - Dec 11 '23 - - Dev Community

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

    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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

    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)
Enter fullscreen mode Exit fullscreen mode

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

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

    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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Результат:

    [a, c, d]
Enter fullscreen mode Exit fullscreen mode

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

    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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

    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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

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

    Collections.synchronizedList(list);
Enter fullscreen mode Exit fullscreen mode

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

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player