当计算a ^ b为什么并行不起作用但parallelStream可以

时间:2017-05-17 00:16:58

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

我想计算一个^ b,例如2 ^ 30,

public long pow(final int a, final int  b)

首先我用这种方式

return LongStream.range(0, b).reduce(1, (acc, x) -> a * acc); // 1073741824

得到了正确的结果。然后我想平行计算它,所以我自然而然地将它改为

return LongStream.range(0, b).parallel().reduce(1, (acc, x) -> a * acc); // 32

但在这种情况下,结果只是32。为什么呢?

所以为了支持并行我再次改变它

return Collections.nCopies(b,a).parallelStream().reduce(1, (acc, x) -> acc * x); // 1073741824

在这种情况下它起作用。

parallel方式有什么问题?

2 个答案:

答案 0 :(得分:5)

reduce要求提供的函数为associative。您的函数(acc, x) -> a * acc不符合要求,因此违反了合同。

要进行关联,对于任何x,y和z,函数必须满足(x op y) op z == x op (y op z)。但是对于您的功能(x op y) op z = x*a^2x op (y op z) = x * a

此外,提供给reduce的第一个参数必须是与累加器函数相关的标识。因此任何x都必须1 op x == x。但是,自1 op x == a以来,这也不适用于累加器函数。

正确的方法是:

LongStream.range(0, b).map(x -> a).reduce(1, (u, v) -> u * v);

无论流是并行还是顺序,都可以保证正常工作。

答案 1 :(得分:1)

在跟踪源代码后,我终于知道为什么结果是32。

return LongStream.range(0, b).parallel().reduce(1, (acc, x) -> a * acc); // 32

相关源代码

   // java.util.stream.ReduceOps.ReduceTask#onCompletion
    @Override
    public void onCompletion(CountedCompleter<?> caller) {
        if (!isLeaf()) {
            S leftResult = leftChild.getLocalResult();
            leftResult.combine(rightChild.getLocalResult()); // here to combine
            setLocalResult(leftResult);
        }

    }

    // java.util.stream.TerminalOp
    @Override
    public void combine(ReducingSink other) {
        accept(other.state);
    }
    @Override
    public void accept(long t) {
        state = operator.applyAsLong(state, t); // (acc, x)
    }

因为实际上没有在lambda中使用x

(acc, x) -> a * acc;

所以实际效果就像这样

leftResult.combine(2); 

Online demo来模拟这种现象。