Есть ли у Collection.stream () внутренняя синхронизация?

Я пытаюсь воспроизвести (и решить) ConcurrentModificationException когда экземпляр HashMap читается и записывается несколькими Thread s.

Отказ от ответственности. Я знаю, что HashMap не является потокобезопасным.

В следующем коде:

import java.util.*;

public class MyClass {

    public static void main(String args[]) throws Exception {
        java.util.Map<String, Integer> oops = new java.util.HashMap<>();
        oops.put("1", 1);
        oops.put("2", 2);
        oops.put("3", 3);

        Runnable read = () -> {
            System.out.println("Entered read thread");

            /*
             * ConcurrentModificationException possibly occurs
             *
            for (int i = 0; i < 100; i++) {
                List<Integer> numbers = new ArrayList<>();
                numbers.addAll(oops.values());
                System.out.println("Size " + numbers.size());
            }
            */

            for (int i = 0; i < 100; i++) {
                List<Integer> numbers = new ArrayList<>();
                numbers.addAll(oops.values()
                        .stream()
                        .collect(java.util.stream.Collectors.toList()));
                System.out.println("Size " + numbers.size());
            }
        };

        Runnable write = () -> {
            System.out.println("Entered write thread");
            for (int i = 0; i < 100; i++) {
                System.out.println("Put " + i);
                oops.put(Integer.toString(i), i);
            }
        };

        Thread writeThread = new Thread(write, "write-thread");
        Thread readThread = new Thread(read, "read-thread");

        readThread.start();
        writeThread.start();

        readThread.join();
        writeThread.join();
    }
}

В принципе, я делаю два потока: один сохраняет элементы в HashMap , другой - итерации на HashMap.values() .

В read потоке, если я использую numbers.addAll(oops.values()) , случайно возникает ConcurrentModificationException . Хотя строки печатаются случайным образом, как ожидалось.

Но если я переключусь на numbers.addAll(oops.values().stream().. , я не получаю никаких ошибок. Однако я заметил странное явление. Все строки read потока печатаются после строк печатается по потоку write .

Мой вопрос в том, имеет ли Collection.stream() внутреннюю синхронизацию?

ОБНОВЛЕНИЕ :

Используя JDoodle https://www.jdoodle.com/a/IYy , похоже на JDK9 и JDK10, я получу ConcurrentModificationException как и ожидалось.

Спасибо!

Всего 3 ответа


То, что вы видите, абсолютно случайно; помните, что внутренне System.out.println выполняет synchronzied ; таким образом, может быть, что-то похоже на то, что результаты выглядят по порядку.

Я не слишком глубоко заглянул в ваш код, потому что анализ того, почему HashMap , который не является потокобезопасным, пропускает поведение, вероятно, бесполезен; как вы знаете, это документально небезопасно.

О том, что ConcurrentModificationException , документация является конкретной, что он будет стараться в лучшем случае, чтобы бросить это; так что это или java-8 было слабее в этом пункте, или это было снова случайно.


Я смог получить ConcurrentModificationException с потоками на Java 8, но с некоторыми изменениями в коде: увеличено количество итераций и количество добавленных элементов для отображения в отдельном потоке от 100 до 10000. Также добавлен CyclicBarrier чтобы циклы в потоках чтения и записи начинаются более или менее одновременно. Я также проверил исходный код spliterator для Hashmap.values() и выдает Hashmap.values() ConcurrentModificationException если были сделаны некоторые изменения в карте.

if (m.modCount != mc) //modCount is number of modifications mc is expected modifications count which is stored before trying to fetch next element
                throw new ConcurrentModificationException();

Я быстро посмотрел на исходный код Java 8, он бросает ConcurrentModificationException . Метод spliterator() метода HashMap возвращает подкласс AbstractCollection , метод spliterator() возвращает ValueSpliterator , который ValueSpliterator ConcurrentModificationException .

Для информации Collection.stream() использует разделитель для перемещения или разделения элементов источника.


Есть идеи?

10000