Почему эта операция отображения упорядоченного потока не сохраняет порядок?

В JavaDoc пакета потока говорится: «Если источником потока является список, содержащий [1, 2, 3], то результат выполнения карты (x -> x*2) должен быть [2, 4». , 6].

Я пытался проверить это поведение, используя следующий код, но результат не соответствует ожиданиям.

Optional<String> result = Stream.of(1, 2, 3, 4).parallel().map(i -> {
    try {
        if (i == 1) {
            Thread.sleep(1000);
        }
    } catch (Exception e) {
    }
    return "" + i;
}).peek(x -> {
    System.out.println(Thread.currentThread().threadId() + " Peeking " + x);
}).findFirst();

System.out.println(result);

Результат:

1 Peeking 3
23 Peeking 4
21 Peeking 2
22 Peeking 1
Optional[1]

Разве исходящий поток операции сопоставления не должен иметь сначала «1»? Кроме того, даже если я вставлю операцию unordered() перед параллелью, результат все равно будет необязательным1. Почему? Разве в этом случае findFirst не должен просто возвращать какое-либо значение вместо того, чтобы ждать завершения операции до 1?

Только когда я удаляю операции unordered() и Parallel(), я вижу вывод как:

1 Peeking 1
Optional[1]

Это означает, что findFirst() выполняет короткое замыкание. Но почему это не короткое замыкание, когда поток неупорядочен и параллелен?

Основываясь на комментарии @user2357112, я попробовал это, изменив операцию просмотра на отображение следующим образом:

map(x -> {
    System.out.println("Remapping " + x);
    return "x"+x;
})

Вывод все еще не в порядке:

Remapping 2
Remapping 4
Remapping 3
Remapping 1
Optional[x1]

«Разве исходящий поток операции сопоставления не должен иметь первым число «1»?» - оно делает. Вот почему у вас есть Optional[1] вместо Optional[3].

user2357112 13.04.2024 06:40
peek параллельный поток не соблюдает порядок.
user2357112 13.04.2024 06:42

@user2357112 user2357112 вы правы, просмотренный Javadoc действительно говорит об этом. Но я изменил просмотр на карту, и результат все еще не в порядке.

Priyshrm 13.04.2024 06:50

Печать вывода из нескольких потоков не является способом продемонстрировать детерминированный порядок, поскольку печать из нескольких потоков сама по себе не имеет детерминированного порядка. Единственный способ продемонстрировать здесь детерминированный порядок — это распечатать результат терминальной операции потока (например, toList()).

Mark Rotteveel 13.04.2024 10:52
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как вы думаете, что делает parallel?

Все операции map над элементами потока выполняются параллельно. Тот факт, что работа идентична, не означает, что все они занимают одинаковое время. Один поток не обязательно должен выполняться на одном и том же ядре эффективности. Или одного прерывают, а другого нет, и так далее. Следовательно, когда все происходит параллельно, порядок исчезает.

Вы не получите свой Optional[1] «первым», потому что все 4 работают параллельно. Java — не машина времени; он не может, как только поймет, что «1» является работоспособным ответом, вернуться назад и задним числом изменить время так, чтобы остальные 3 никогда не начинались.

Вы можете подумать: Ну, первое (помните, «первого» не существует, не тогда, когда мы говорим parallel(), а гипотетически) пройдет, потому что мы просто просим любого результата. Но потоковый API этого не знает. Возможно, вы намереваетесь запустить filter для всего, что выходит из map, и в этом вся суть параллели: запустить все 4 операции с картами одновременно, а затем применить фильтрацию ко всем 4 одновременно. Что бы вы ни выжили — возьмите первым то, что окажется на финише (.findFirst()), и верните это.

Почему он возвращает необязательный параметр? По той же причине. Что, если все четверо не пройдут filter? Тогда никаких элементов не останется. Тот факт, что у вас нет filter, не имеет значения. Там может быть один. API написан с учетом этого. Попытайтесь создать систему типов, которая может представлять как «потоковая операция гарантированно возвращает хотя бы один результат», так и «потоковая операция может не возвращать никаких результатов», и посмотрите, как далеко вы продвинетесь. (далеко не уедешь).

Да, я понимаю, что операция сопоставления будет происходить параллельно. Но я спрашиваю о конечном результате. Это следует заказать для упорядоченного потока, верно? Я понимаю, что невозможно определить порядок объектов, сгенерированных в исходящем потоке промежуточной операции. Во-вторых, если я вставлю операцию unordered() перед параллельным(), результат по-прежнему будет необязательным[1]. Почему? Почему он ждет завершения операции сопоставления 1, когда другие уже завершились? findFirst в неупорядоченном потоке должен иметь возможность возвращать любой элемент, верно?

Priyshrm 13.04.2024 07:38

Добавление unordered() помечает конвейер потока как неупорядоченный. Чтобы изменить порядок элементов, не нужно перетасовывать. Также он не ждет завершения операции сопоставления 1. То, что вы видите, является результатом параллельных операций. Попробуйте использовать большее количество элементов, и вы сможете убедиться, что некоторые элементы останутся необработанными (т. е. не дойдут до стадии карты).

Thiyagu 13.04.2024 08:01

@Priyshrm Порядок сохраняется в терминальной операции, а не в промежуточных операциях: если «параллельный, но упорядоченный» поток также упорядочивает промежуточные элементы, он не будет параллельным! Следовательно, почему просмотр (который является промежуточной операцией) не показывает порядок. Вы видите поведение ярлыков. Добавьте в этот поток достаточно вещей, и в нем не будут peek всех элементов. Но, поскольку в потоке 4, все 4 «начались» (и дошли до просмотра) до того, как findFirst() пошел: На этом можно закончить, у меня есть ответ.

rzwitserloot 13.04.2024 13:47
Ответ принят как подходящий

Результат:

1 Peeking 3
23 Peeking 4
21 Peeking 2
22 Peeking 1
Optional[1]

Разве исходящий поток операции сопоставления не должен иметь сначала «1»?

Нет.

Вы запрашиваете ПОРЯДОК ИСПОЛНЕНИЯ: сначала начните 1, затем начните 2 и так далее.

Нет. Параллельные потоки по определению НЕ задают порядок выполнения. ДАЖЕ ЕСЛИ ВЫ ПОСТАВИТЕ ordered() НА СВОЙ ПАРАЛЛЕЛЬНЫЙ ПОТОК, ВЫ НЕ ПОЛУЧИТЕ ИСПОЛНЕНИЕ.

Поставив ordered() на ленту, вы получите ПОРЯДОК РЕЗУЛЬТАТА. Это означает, что ваш поток будет упорядочивать результаты, КАК ОНИ ДОСТИГНУТ НА ТЕРМИНАЛЬНУЮ РАБОТУ. Не раньше.

Кроме того, даже если я вставлю операцию unordered() перед параллелью, результат все равно будет Optional[1]. Почему? Разве в этом случае findFirst() не следует просто возвращать любое значение вместо того, чтобы ждать завершения операции 1?

Я попробовал это сам и получил Optional[3]. Я использую версию Java 23 с ранним доступом. Можете ли вы попробовать запустить для меня приведенный ниже фрагмент и посмотреть, получите ли вы Optional[1] в качестве ответа?

Optional<String> result = Stream.of(1, 2, 3, 4).unordered().parallel().map(i -> {
    try {
        if (i == 1) {
            Thread.sleep(1000);
        }
    } catch (Exception e) {
    }
    return "" + i;
}).peek(x -> {
    System.out.println(Thread.currentThread().threadId() + " Peeking " + x);
}).findFirst();

Если вы получили Optional[1], сообщите мне также свою операционную систему, версию Java и дистрибьютора (Oracle, Amazon и т. д.).

Только когда я удаляю операции unordered() и Parallel(), я вижу результат

Итак, вы делаете Stream.of(). Это ваш источник. И у него есть порядок встреч, то есть это то же самое, что сказать ordered() в вашей трансляции. Поэтому становится понятно, почему он схватил Optional[1].

Кстати, я вам говорил, что если вы воспользуетесь parallel(), вы НЕ МОЖЕТЕ получить ИСПОЛНИТЕЛЬНЫЙ ПОРЯДОК. Если вам нужен ПОРЯДОК ВЫПОЛНЕНИЯ, вам следует использовать непараллельный поток, также известный как последовательный поток. Последовательный означает, что вы делаете только 1 за раз. ЭТО даст вам ПРИКАЗ ИСПОЛНЕНИЯ. Вот почему результат такой, как вы и ожидали, потому что Stream.of() — это ПОСЛЕДОВАТЕЛЬНЫЙ поток. Итак, вы получаете ИСПОЛНИТЕЛЬНЫЙ ПРИКАЗ. Но в результате вы теряете параллелизм.

ВЫ НЕ МОЖЕТЕ ИМЕТЬ ПОРЯДОК ИСПОЛНЕНИЯ И ПАРАЛЛЕЛЬНОСТЬ ВМЕСТЕ. Если вы этого хотите, создайте собственное решение или сделайте что-нибудь нестандартное.

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