Статическая коллекция, совместно используемая поведением нескольких потоков - PullRequest
0 голосов
/ 10 мая 2018

Для приведенного ниже фрагмента кода:

public class ThreadExample extends Thread {

    static List<Integer> myList = new CopyOnWriteArrayList<Integer>();

    public static void main(String[] args) throws InterruptedException {
        myList.add(11);
        myList.add(22);
        myList.add(33);
        myList.add(44);
        ThreadExample e = new ThreadExample();
        e.start();
        for (Integer it : myList) {
            try {
                Thread.sleep(1000);
            } catch (Exception e1) {
                System.out.print("e1 ");
            }
            System.out.print(" " + it);
        }
    }

    public void run() {
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            System.out.print("e2 ");
        }
        myList.add(77);
        System.out.print("size: " + myList.size() + ", elements:");
    }
}

Код всегда печатает выходные данные в виде: size: 5, elements: 11 22 33 44

Я не могу понять это поведение.Когда рабочий поток изменяет статический список, добавляя новый элемент, размер которого правильно отображается, тогда почему основной поток печатает только 4 элемента?Учитывая, что рабочий поток выполняется первым всегда (время ожидания там меньше).

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

Это потому, что в вашем for (Integer it : myList) вы создаете Iterator поверх CopyOnWriteArrayList<Integer> и из документа Java:

Метод итератора в стиле «снимок» использует ссылку насостояние массива в момент создания итератора.Этот массив никогда не изменяется в течение времени жизни итератора, поэтому вмешательство невозможно, и итератор гарантированно не генерирует исключение ConcurrentModificationException.Итератор не будет отражать добавления, удаления или изменения в списке с момента его создания.

Таким образом, основной поток не увидит обновление из потока e, поскольку вВ большинстве случаев (вы спите в методе run()) Iterator создается до того, как поток e добавляет новый Integer.

Дополнительная информация вдокументация .

0 голосов
/ 10 мая 2018

A CopyOnWriteArrayList предоставляет вам поточно-ориентированные итераторы, так как вы можете выполнять итерацию списка во время его одновременного изменения, не выбрасывая ConcurrentModificationException.

Логически (не на самом деле, потому что это неэффективно и не поточно-ориентировано), итерация CopyOnWriteArrayList выглядит следующим образом:

List<Integer> copyOfList = new ArrayList<>(myList);
for (Integer it : copyOfList) {
  // ...
}

Если какое-либо обновление списка происходит во время итерации, это не имеет значения: вы выполняете итерацию «копии» списка в начале итерации.

Вы ненадолго спите в теме перед добавлением элемента; но к тому времени основной поток уже начал перебирать список.

Поскольку список изначально имеет 4 элемента, итерация списка начинается до того, как поток добавляет элемент, ваш цикл выведет 4 элемента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...