java.util.ConcurrentModificationException Streams - PullRequest
0 голосов
/ 25 декабря 2018

Я пробовал следующий код Java 8 SE Я запустил его непосредственно из Eclipse, у него есть исключение, упомянутое ниже, и я запустил его с командной строкой, он выдает тот же результат.

List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test = test.subList(0, 2);
Stream<String> s = test.stream();
test.add("d");
s.forEach(System.out::println);

Я не уверен, почему именно это дает следующее исключение

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)

Java-версия, с которой я работаю

java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

Ответы [ 4 ]

0 голосов
/ 25 декабря 2018

Минимальный код

List<String> test = new ArrayList<>(Arrays.asList("java-8", "subList", "bug")).subList(0, 2);
Stream<String> stream = test.stream();
test.add("java-9");
stream.forEach(System.out::println); // any terminal operation

Java-8 [Bug]

Приведенный выше код, выполняемый с Java-8, генерирует CME.Согласно javadoc из ArrayList

Итераторы, возвращаемые методами итератора этого класса и listIterator, являются fail-fast : если список структурно измененв любое время после создания итератора, любым способом, кроме как через собственные методы удаления или добавления итератора, итератор выдаст ConcurrentModificationException.

Таким образом, перед одновременной модификацией итератор быстро и чисто дает сбой, вместо того, чтобы рисковать произвольным недетерминированным поведением в неопределенное время в будущем.

Вывод :

java-8
subList
Exception in thread "main" java.util.ConcurrentModificationException

Вопрос

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

Но тогда вопрос заключался в том, что в приведенном выше коде мы фактически закончили изменять коллекцию во время ее итерацииа точнее до этого?

не должен ли поток быть ленивым?

При дальнейшем поиске такого ожидаемого поведения обнаружил что-то похожее, о котором сообщалось, и исправил как ошибку - ArrayList.subList (). Spliterator () не имеет позднего связывания , и это было исправлено в Java-9.

Еще одна ошибка, связанная с этим - ArrayList.subList (). Iterator (). ForEachRemaining () off-by-one-error

Java-11 [Исправлено]

Хотя исправлено в Java-9 в соответствии с отчетом об ошибке, реальный тест, который я выполнял, был на версии LTS, и код, как указано выше, работает без каких-либо исключений.

Вывод :

java-8
subList
java-9
0 голосов
/ 25 декабря 2018

Проблема заключается в том, что вы изменяете содержимое ArrayList, когда поток используется (не закрыт).

    Stream s = test.stream();
    test.add("d"); <<<- here
    s.forEach(System.out::println);

Как упоминается в javadoc ConcurrentModificationException:

Например, как правило, недопустимо, чтобы один поток изменял коллекцию, в то время как другой поток итерирует по ней.

Это верно и для потоков, так как в простом случае они основаны на итераторах.

Кроме того,

Обратите внимание, что это исключение не всегда означает, что объект был одновременно изменен другим потоком.

0 голосов
/ 25 декабря 2018

Это из-за subList, позвольте мне объяснить с различными сценариями

Из документов Java документов

Для скважиныПри использовании потоковых источников, источник может быть изменен до начала работы терминала, и эти изменения будут отражены в охватываемых элементах.

За исключением операций escape-hatch, iterator () и spliterator (), выполнение начинается, когдаоперация терминала вызывается и заканчивается, когда операция терминала завершается.

Случай 1: Успешно (поскольку источник может быть изменен до начала работы терминала)

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    //test = test.subList(0, 2);
    Stream s = test.stream();
    test.add("d");
    s.forEach(System.out::println);

Выход:

A
B
c
d

Случай 2: Сбой для sublist другой эталон

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    List<String> test1 = test.subList(0, 2);
    Stream s = test1.stream();
    test1.add("d");
    s.forEach(System.out::println);

Выход:

A
BException in thread "main" 
java.util.ConcurrentModificationException
at 
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:30)

case 3: Не удалось для sublist одна и та же ссылка, оба списка различны

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    System.out.println(test.hashCode()); //94401
    test = test.subList(0, 2);
    System.out.println(test.hashCode()); //3042
    Stream s = test.stream();
    test.add("d");
    s.forEach(System.out::println);

Выход:

94401
3042
A
B
Exception in thread "main" java.util.ConcurrentModificationException
at 
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:32)

Окончательное заключение

Семантика списка, возвращаемого этим методом, становится неопределенной, если список поддержки (т. Е. Этот список) структурно изменяется любым способом, отличным от возвращаемого списка,(Структурные изменения - это изменения, которые изменяют размер этого списка или иным образом нарушают его таким образом, что выполняемые итерации могут дать неверных результатов. )

Из списка документов SubList Документы

0 голосов
/ 25 декабря 2018

Один из способов получить это исключение - обновить базовый список после создания из него потока, что и произошло здесь.

Просто выполните все ваши add вызовы до вызова stream(), и выдолжно быть хорошо:

List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test.add("d"); // Moved here
test = test.subList(0, 2);
Stream s = test.stream();
// test.add("d") removed here
s.forEach(System.out::println);
...