Java Stream:forEach和forEachOrdered之间的区别

Java Stream: difference between forEach and forEachOrdered

前提:我已经阅读了这个问题和其他问题,但我需要一些澄清。

我知道Stream.forEach方法在处理并行流时会产生差异(不仅仅是这个),这就解释了为什么会这样

1
2
3
4
//1
Stream.of("one","two","three","four","five","six")
.parallel()
.forEachOrdered(item -> System.out.print(item));

版画

1
one two three four five six

但是当涉及到中间操作时,当并行化流时,不再保证顺序。 所以这段代码

1
2
3
4
5
//2
Stream.of("one","two","three","four","five","six")
.parallel()
.peek(item -> System.out.print(item))
.forEachOrdered(item -> System.out.print(""));

会印出类似的东西

1
four six five one three two

forEachOrdered方法只影响其自身执行中元素的顺序是否正确? 直觉上,我认为//1的例子完全相同

1
2
3
4
5
6
//3
Stream.of("one","two","three","four","five","six")
.parallel()
.peek(item -> System.out.print("")) //or any other intermediate operation
.sequential()
.forEach(item -> System.out.print(item));

我的直觉是错的吗? 我错过了整个机制的一些内容吗?


你是对的,因为forEachOrdered的行动所做的保证只适用于那个动作而没有其他任何内容。但是假设这与.sequential().forEach(…)相同是错误的。

sequential会将整个流管道转换为顺序模式,因此,传递给forEach的操作将由同一个线程执行,但也会执行前面的peek操作。对于大多数中间操作,parallelsequential的确切位置是无关紧要的,并指定两者都没有意义,因为只有最后一个是相关的。

此外,即使在当前实现中没有任何后果,使用forEach时仍然无法保证订购。这在"Stream.forEach是否遵循顺序流的遭遇顺序?"中讨论。

Stream.forEachOrdered的文档说明:

This operation processes the elements one at a time, in encounter order if one exists. Performing the action for one element happens-before performing the action for subsequent elements, but for any given element, the action may be performed in whatever thread the library chooses.

因此,动作可能会被不同的线程调用,如Thread.currentThread()可感知但不会同时运行。
此外,如果流具有遭遇顺序,则它将在此处重构。这个答案揭示了遭遇顺序和处理顺序的区别。


#3代码在概念上不等同于#1,因为parallel()sequential()调用影响整个流,而不仅仅影响后续操作。因此在#3情况下,整个过程将按顺序执行。实际上#3案例类似于实际可以更改并行/顺序模式时Stream API的早期设计。这被认为是不必要的复杂化(并且实际上添加了问题,例如,参见此讨论),因为通常您只需要更改模式以使终端操作有序(但不一定是顺序的)。因此添加了forEachOrdered()并更改了parallel()/sequential()语义以影响整个流(请参阅此变更集)。

基本上你是对的:在并行流中,没有中间操作的订单保证。如果需要按特定顺序执行它们,则必须使用顺序流。