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.

Top comments (1)

Collapse
 
niftim profile image
Ivan

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