В 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]
peek
параллельный поток не соблюдает порядок.
@user2357112 user2357112 вы правы, просмотренный Javadoc действительно говорит об этом. Но я изменил просмотр на карту, и результат все еще не в порядке.
Печать вывода из нескольких потоков не является способом продемонстрировать детерминированный порядок, поскольку печать из нескольких потоков сама по себе не имеет детерминированного порядка. Единственный способ продемонстрировать здесь детерминированный порядок — это распечатать результат терминальной операции потока (например, toList()
).
Как вы думаете, что делает parallel
?
Все операции map
над элементами потока выполняются параллельно. Тот факт, что работа идентична, не означает, что все они занимают одинаковое время. Один поток не обязательно должен выполняться на одном и том же ядре эффективности. Или одного прерывают, а другого нет, и так далее. Следовательно, когда все происходит параллельно, порядок исчезает.
Вы не получите свой Optional[1]
«первым», потому что все 4 работают параллельно. Java — не машина времени; он не может, как только поймет, что «1» является работоспособным ответом, вернуться назад и задним числом изменить время так, чтобы остальные 3 никогда не начинались.
Вы можете подумать: Ну, первое (помните, «первого» не существует, не тогда, когда мы говорим parallel()
, а гипотетически) пройдет, потому что мы просто просим любого результата. Но потоковый API этого не знает. Возможно, вы намереваетесь запустить filter
для всего, что выходит из map
, и в этом вся суть параллели: запустить все 4 операции с картами одновременно, а затем применить фильтрацию ко всем 4 одновременно. Что бы вы ни выжили — возьмите первым то, что окажется на финише (.findFirst()
), и верните это.
Почему он возвращает необязательный параметр? По той же причине. Что, если все четверо не пройдут filter
? Тогда никаких элементов не останется. Тот факт, что у вас нет filter
, не имеет значения. Там может быть один. API написан с учетом этого. Попытайтесь создать систему типов, которая может представлять как «потоковая операция гарантированно возвращает хотя бы один результат», так и «потоковая операция может не возвращать никаких результатов», и посмотрите, как далеко вы продвинетесь. (далеко не уедешь).
Да, я понимаю, что операция сопоставления будет происходить параллельно. Но я спрашиваю о конечном результате. Это следует заказать для упорядоченного потока, верно? Я понимаю, что невозможно определить порядок объектов, сгенерированных в исходящем потоке промежуточной операции. Во-вторых, если я вставлю операцию unordered() перед параллельным(), результат по-прежнему будет необязательным[1]. Почему? Почему он ждет завершения операции сопоставления 1, когда другие уже завершились? findFirst в неупорядоченном потоке должен иметь возможность возвращать любой элемент, верно?
Добавление unordered() помечает конвейер потока как неупорядоченный. Чтобы изменить порядок элементов, не нужно перетасовывать. Также он не ждет завершения операции сопоставления 1. То, что вы видите, является результатом параллельных операций. Попробуйте использовать большее количество элементов, и вы сможете убедиться, что некоторые элементы останутся необработанными (т. е. не дойдут до стадии карты).
@Priyshrm Порядок сохраняется в терминальной операции, а не в промежуточных операциях: если «параллельный, но упорядоченный» поток также упорядочивает промежуточные элементы, он не будет параллельным! Следовательно, почему просмотр (который является промежуточной операцией) не показывает порядок. Вы видите поведение ярлыков. Добавьте в этот поток достаточно вещей, и в нем не будут peek
всех элементов. Но, поскольку в потоке 4, все 4 «начались» (и дошли до просмотра) до того, как findFirst()
пошел: На этом можно закончить, у меня есть ответ.
Результат:
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()
— это ПОСЛЕДОВАТЕЛЬНЫЙ поток. Итак, вы получаете ИСПОЛНИТЕЛЬНЫЙ ПРИКАЗ. Но в результате вы теряете параллелизм.
ВЫ НЕ МОЖЕТЕ ИМЕТЬ ПОРЯДОК ИСПОЛНЕНИЯ И ПАРАЛЛЕЛЬНОСТЬ ВМЕСТЕ. Если вы этого хотите, создайте собственное решение или сделайте что-нибудь нестандартное.
«Разве исходящий поток операции сопоставления не должен иметь первым число «1»?» - оно делает. Вот почему у вас есть
Optional[1]
вместоOptional[3]
.