DEV Community

faangmaster
faangmaster

Posted on

Модификация коллекции 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);
        }
    }
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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (1)

Collapse
 
niftim profile image
Ivan

Добрый день!
Очень тяжело дается чтение книги "Java Concurrency in Action".
Какую еще книгу могли бы посоветовать по этой теме?
P.s.: Спасибо за столь полезные посты!

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay