并行流与串行流

并行流是否有可能给出与 Java 8 中的串行流不同的结果?根据我的信息,并行流与串行流相同,只是分为多个子流。这是一个速度问题。完成对元素的所有操作,并在最后合并子流的结果。最后,在我看来,并行流和串行流的操作结果应该是相同的。所以我的问题是,这个代码有没有可能给我一个不同的结果?如果这是可能的,为什么会发生这种情况?

int[] i = {1, 2, 5, 10, 9, 7, 25, 24, 26, 34, 21, 23, 23, 25, 27, 852, 654, 25, 58};
Double serial = Arrays.stream(i).filter(si -> {
    return si > 5;
}).mapToDouble(Double::new).map(NewClass::add).reduce(Math::atan2).getAsDouble();

Double parallel = Arrays.stream(i).filter(si -> {
    return si > 5;
}).parallel().mapToDouble(Double::new).map(NewClass::add).reduce(Math::atan2).getAsDouble();

System.out.println("serial: " + serial);
System.out.println("parallel: " + parallel);

public static double add(double i) {
    return i + 0.005;
}

结果是:

serial: 3.6971567726175894E-23

parallel: 0.779264049587662

答案 1

用于 reduce() 的 javadoc 说:

使用关联累积函数对此流的元素执行缩减, [...]累加器函数必须是关联函数。

“关联”这个词链接到这个java文档:

如果以下情况成立,则运算符或函数 op 是关联的:

 (a op b) op c == a op (b op c)

如果我们将其扩展到四个术语,则可以看出它对并行评估的重要性:

 a op b op c op d == (a op b) op (c op d)

因此,我们可以与(c op d)并行评估(a op b),然后对结果调用op。

关联运算的示例包括数值加法、最小值和最大值以及字符串串联。

正如注释中提到的@PaulBoddington,它不是关联的,因此对于约简操作无效。atan2


不相关的

您的流序列有点偏差。您应该在并行操作进行过滤,lambda可以缩短,并且您不应该将双精度框:

double parallel = Arrays.stream(i)
                        .parallel()           // <-- before filter
                        .filter(si -> si > 5) // <-- shorter
                        .asDoubleStream()     // <-- not boxing
                        .reduce(Math::atan2)
                        .getAsDouble();

答案 2

与并行流一起使用时,操作不会按特定顺序完成。reduce

因此,如果您希望并行流产生可预测的结果,则无论以何种顺序执行操作,您的 reduce 操作都必须具有相同的答案。

例如,使用加法进行减少是有意义的,因为加法是关联的。无论你做哪一个都没关系,答案是在这两种情况下。6

(1 + 2) + 3
1 + (2 + 3)

atan2不关联。

Math.atan2(Math.atan2(1, 2), 3) == 0.15333604941031637

Math.atan2(1, Math.atan2(2, 3)) == 1.0392451500584097

推荐