【问题标题】:Difference between forEachOrdered() and sequential() methods of Java 8?Java 8的forEachOrdered()和sequential()方法的区别?
【发布时间】:2017-12-14 07:38:40
【问题描述】:

我正在研究 java 8 并行流,并且希望以某种顺序打印并行流中的元素(例如插入顺序、反向顺序或顺序)。

为此我尝试了以下代码:

        System.out.println("With forEachOrdered:");
        listOfIntegers
            .parallelStream()
            .forEachOrdered(e -> System.out.print(e + " "));
        System.out.println("");

        System.out.println("With Sequential:");
        listOfIntegers.parallelStream()
                    .sequential()
                    .forEach(e -> System.out.print(e + " "));

对于这两个,我得到了相同的输出如下:

With forEachOrdered:
1 2 3 4 5 6 7 8 
With Sequential:
1 2 3 4 5 6 7 8 

从 api 文档中,我可以看到:

forEachOrdered -> 这是一个终端操作。

sequential -> 这是一个中间操作。

所以我的问题是哪个更好用? 在哪些情况下,应该优先选择一个?

【问题讨论】:

    标签: java parallel-processing java-8 java-stream


    【解决方案1】:

    listOfIntegers.parallelStream().sequential().forEach() 创建一个并行的Stream,然后将其转换为一个顺序的Stream,因此您不妨改用listOfIntegers.stream().forEach(),并首先获得一个顺序的Stream

    listOfIntegers.parallelStream().forEachOrdered(e -> System.out.print(e + " ")) 对并行的Stream 执行操作,但保证元素将按照Stream 的遇到顺序被消耗(如果Stream 具有定义的遇到顺序)。但是,它可以在多个线程上执行。

    我没有看到使用listOfIntegers.parallelStream().sequential() 的理由。如果你想要一个连续的Stream,为什么要先创建一个并行的Stream

    【讨论】:

    • 因为您可能希望并行完成一些繁重的操作,然后对流重新排序以获得最终输出
    • @AdrianShum 我不确定这在性能方面会有多好,因为流是懒惰地评估的。它不会为所有元素并行执行第一个中间步骤,然后在顺序流上继续执行其余的中间步骤和终端操作。只有当你到达终端操作时,它才会开始对各个元素执行中间操作,直到它们成为终端操作的输入。
    • 确实如此。也许只有在我编写一些可以在提供的流上工作的实用程序的情况下才有用,并且即使用户提供并行流,我也想确保它是顺序的?
    • list.parallelStream().sorted().forEachOrdered(…) 视为一个实际示例,它并行执行繁重的工作,但执行有序的终端操作。
    • 没错。这是.parallelStream(). … .forEachOrdered(…).parallelStream(). … sequential().forEach(…) 表现出明显差异的操作示例。正如您所说,“流是懒惰地评估的”,因此如果终端操作由于排序约束而阻塞工作线程,除非您有像sorted() 这样的有状态中间操作,否则您不会获得太多并行性。
    【解决方案2】:

    你问的是一个误导性的问题,首先你问的是:

     .parallelStream()
     .forEachOrdered(...)
    

    这将创建一个并行流,但元素将被按顺序使用。如果你像这样添加map 操作:

    .map(...)
    .parallelStream()
    .forEachOrdered(...)
    

    这将使map 的操作非常有限(从并行处理的角度来看),因为线程必须等待遇到的所有其他元素才能被处理(由forEachOrdered 消耗)。这涉及无状态操作。

    另一方面,如果您有 有状态操作,例如:

    .parallelStream()
    .map()
    .sorted()
    .// other operations
    

    由于sorted 是有状态的,无状态操作在它之前从并行处理中的好处会更大。发生这种情况是因为sorted 必须从 Stream 中收集所有元素,并且线程不必“等待”(在forEachOrdered)以遇到顺序的元素。

    第二个例子:

    listOfIntegers.parallelStream()
                    .sequential()
                    .forEach(e -> System.out.print(e + " "))
    

    您基本上是说打开并行然后将其关闭。流是由终端操作驱动的,所以即使你这样做:

     .map...
     .filter...
     .parallel()
     .map...
     .sequential
    

    这意味着整个管道将按顺序执行,而不是某些部分是并行的,而另一个是顺序的。您还依赖于 forEach 保留顺序这一事实,并且可能目前确实如此,但可能在以后的版本中,因为您说您不关心顺序(首先使用 forEach ),会有一个内部的元素洗牌。

    【讨论】:

      【解决方案3】:

      流管道可以按顺序或并行执行。这种执行模式是流的属性。流是通过初始选择顺序或并行执行来创建的。例如,Collection.stream() 创建一个顺序流,Collection.parallelStream() 创建一个并行流。这种执行模式的选择可以通过BaseStream.sequential()BaseStream.parallel() 方法进行修改。

      所以没有必要使用:

      listOfIntegers.parallelStream().sequential()
      

      你只能使用:

      listOfIntegers.stream()
      

      如果您正在创建parallel stream,则流的元素可能由不同的线程处理。 forEachforEachOrdered 之间的区别在于 forEach 将允许以任何顺序处理并行流的任何元素,而 forEachOrdered 将始终按照它们在原始流中出现的顺序处理并行流的元素。当使用parallelStream()forEachOrdered 时,这是一个很好的例子,说明如何利用多个内核并仍然保持输出的顺序。请注意,forEachOrdered 强制以有序方式迭代流的元素。但是,在 forEachOrdered 之前链接的任何操作仍将并行发生,因为流是并行流。

      Oracle 没有准确记录当您在管道中多次更改流执行模式时会发生什么。不清楚是最后一次更改重要还是调用parallel() 后调用的操作可以并行执行,调用sequential() 后调用的操作将按顺序执行。

      【讨论】:

      • “当您在管道中多次更改流执行模式时,没有确切记录会发生什么”。我不同意,Stream 的类文档中已明确说明“流管道可以顺序或并行执行”
      • @AnlonBurke 请仔细阅读 Oracle 未记录的最终内容。
      • 整个管道要么顺序执行并行执行。恕我直言,这就是 Javadoc 中的这句话所说的。不可能一部分是parallel,另一部分是sequential(实际上,在Stream API的初始开发过程中尝试了一段时间,但因为太复杂而放弃了)。我不明白您对此的怀疑来自哪里。
      猜你喜欢
      • 2016-03-28
      • 2015-12-24
      • 2014-12-28
      • 2022-10-31
      • 1970-01-01
      • 2018-06-09
      • 2017-10-26
      • 2020-03-03
      相关资源
      最近更新 更多