Понимание параллельного потока и порядка коллектора

Я пытаюсь понять, почему происходит другое, когда я меняю с y.addAll(x) на x.addAll(y) в фрагменте кода ниже:

List<Integer> result = List.of(1, 2)
    .parallelStream() 
    .collect(
        ArrayList::new,
        (x, y) -> x.add(y),
        (x, y) -> y.addAll(x)
    );
System.out.println(result);

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

collect имеет три параметра; первые два параметра я понимаю. Что касается третьего параметра, я знаю, что x, y — это подпотоки и они ArrayList, но я не понимаю, почему результаты в каждом случае разные. Я ожидал, что они будут одинаковыми.

  • (x, y) -> y.addAll(x) // output: [1]

  • (x, y) -> x.addAll(y) // output: [1, 2]

К вашему сведению, вы можете заменить List::add на (x, y) -> x.add(y). То же самое и с addAll.

shmosel 05.08.2024 08:27

потрясающе, спасибо

happy 05.08.2024 14:37
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
11
2
624
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Почему одно правильно, а другое нет

Из Javadocs Stream#collect (в частности, последний параметр, выделено мной):

Комбайнер — ассоциативная, неинтерферирующая функция без сохранения состояния, которая принимает два контейнера частичных результатов и объединяет их, что должно быть совместимо с функцией аккумулятора. Функция объединителя должна сложить элементы из второго контейнера результатов в первый контейнер результатов.

Аналогично, a.addAll(b) добавляет все элементы от b до a, но не наоборот. Он берет информацию из параметра и модифицирует приемник.

Итак, контракт этого метода указывает, что вам необходимо объединить второй аргумент лямбды с первым.

Если вы это сделаете (x, y) -> x.addAll(y), все элементы y будут добавлены в x в соответствии с договором. Однако с помощью (x, y) -> y.addAll(x) вы добавляете его ко второму элементу, в результате чего элементы y не добавляются к x, а затем отсутствуют в результате.

Что происходит

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

Допустим, у нас есть числа 1 и 2, как в вашем примере, и предположим, что один поток обрабатывает фрагмент, содержащий 1, а другой поток обрабатывает фрагмент, содержащий 2. При сборе каждый поток начинается с создания нового ArrayList после ArrayList::new в вашем коде. Затем потоки добавляют элементы соответствующих фрагментов в список, в результате чего получается два списка по одному элементу в каждом (1 для первого потока и 2 для другого). Когда оба потока завершают работу, вызывается объединитель для слияния/объединения результатов. С помощью x.addAll(y) он добавляет второй список к первому, который затем возвращается с правильным результатом. Однако с помощью y.addAll(x) элементы первого списка добавляются во второй список, но Java предполагает, что вам нужен первый список (поскольку это то, что вы должны изменить), поэтому сбор возвращает первый список, который не содержит элементов, обработанных вторая ветка.

ответ идеальный: я знаю, что должен следовать контракту. Я хочу узнать больше об этом: «Вы добавляете его ко второму элементу, в результате чего элементы y не добавляются к x, а затем отсутствуют в результате». почему нам нужно заботиться о добавлении к x или добавлении к y?

happy 03.08.2024 19:58

@happy, потому что collect() вернет первый параметр, то есть x -- добавление к y не изменит результат (его можно было запрограммировать на возврат результата последнего параметра, но это не сработает, например, с addAll() поскольку он возвращает логическое значение)

user85421 03.08.2024 20:10

@happy Способ реализации потока: он сохраняет x (после добавления в него каких-либо элементов) и отбрасывает y. Добавив все элементы x в y, поток сохранит исходный x без каких-либо элементов y.

M. Justin 03.08.2024 20:11

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

dan1st 03.08.2024 20:12

Чтобы уточнить, в вашем случае это выглядит так: x было [1] и y было [2]. После добавления 1 к yx по-прежнему остается [1], то есть значение, которое использует и возвращает реализация потока, и, следовательно, это общий результат.

M. Justin 03.08.2024 20:13

идеальный. Спасибо всем

happy 03.08.2024 20:22

Обратите внимание, что это отличается от определения Collector, где вы можете делать с параметрами все, что захотите, при условии, что вы возвращаете их комбинацию.

shmosel 05.08.2024 18:32

@shmosel вы имеете в виду: разницу между объединителем в Collect() Stream и объединителем в Collector.class

happy 06.08.2024 08:01

Я переименовал некоторые из ваших переменных, чтобы лучше проиллюстрировать, что происходит.

List<Integer> result = List.of( 1, 2 )
    .parallelStream() 
    .collect(
        ArrayList::new,
        (thisList,value) -> thisList.add( value ),
        (thisList,theOtherList) -> thisList.addAll( theOtherList )
    );
System.out.println( result );

Метод Stream::collect возвращает thisList.

Когда вы измените третий параметр вызова на collect() вот так:

…
(thisList,theOtherList) -> theOtherList.addAll( thisList )
…

вы добавляете содержимое thisList к theOtherList, а thisList остается неизменным, то есть содержимое theOtherList к нему не добавляется.

То, что результат всего лишь 1, случайно.

Другие вопросы по теме