为什么并行流在 Java 8 中按顺序收集

Why parallel stream get collected sequentially in Java 8

为什么 forEach 以随机顺序打印数字,而 collect 总是按原始顺序收集元素,即使是并行流?

Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8};
List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray));

System.out.println("Parallel Stream: ");
listOfIntegers
  .stream()
  .parallel()
  .forEach(e -> System.out.print(e + " "));
System.out.println();

// Collectors         
List<Integer> l = listOfIntegers
  .stream()
  .parallel()
  .collect(Collectors.toList());
System.out.println(l);

输出:

Parallel Stream: 
8 1 6 2 7 4 5 3 
[1, 2, 3, 4, 5, 6, 7, 8]

Collectors.toList method 指定返回的 Collector 将元素添加到列表中的顺序。

Returns:

a Collector which collects all the input elements into a List, in encounter order

Stream是否平行并不重要;顺序被保留。

此外,查看 Collectors 源代码,返回的 Collector 在合并时调用 ArrayList 上的 addAll,并且保留顺序。例如。如果一个线程有 {1, 2} 而下一个线程有 {3, 4},那么调用 addAll 会产生 {1, 2, 3, 4}。此外,返回的 Collector 没有 UNORDERED 特征。

这里有两种不同的 "ordering",这使讨论变得混乱。

一种是遇到命令,定义在streams documentation中。考虑这一点的一个好方法是源集合中元素的 spatialleft-to-right 顺序。如果源是 List,请考虑较早的元素位于较晚的元素的左侧。

还有processing or temporal order,文档中没有定义,但是是时间顺序元素由不同的线程处理。如果列表的元素由不同的线程并行处理,则线程可能会先处理列表中最右边的元素,然后再处理最左边的元素。但是下一次可能就不会了。

即使计算是并行完成的,大多数 Collectors 和一些终端操作都经过精心安排,以便它们保留从源到目的地的 遇到顺序 ,与不同线程可能处理每个元素的 时间顺序 无关。

请注意,forEach 终端操作不会 保留遇到顺序。相反,它是 运行 由任何线程碰巧产生下一个结果。如果你想要像 forEach 这样保留相遇顺序的东西,请改用 forEachOrdered

另请参阅 Lambda FAQ 以进一步讨论排序问题。