使用并行流时,减少行为很奇怪,但在Java 8u5中适用于顺序流

时间:2014-06-13 23:54:50

标签: java concurrency parallel-processing java-8

class Foo{
    int len;
}
public class Main {
    public static void main(String[] args) throws Exception{
    System.out.println(Stream.of("alpha", "beta", "gamma", "delta").parallel().reduce(
            new Foo(),
            (f, s) -> { f.len += s.length(); return f; },
            (f1, f2) -> {
                Foo f = new Foo();
                /* check self-reduction
                if (f1 == f2) { 
                    System.out.println("equal");
                    f.len = f1.len;
                    return f;
                }
                */
                f.len = f1.len + f2.len;
                return f;
            }
    ).len);
}

代码尝试计算几个字符串的总长度。

这段代码只有在打印时才会打印19
1.我使用顺序流(通过删除" parallel()"函数调用)
要么
2.我使用Integer而不是Foo,它只是一个int的包装器。

否则控制台将打印20或36。为了调试这个问题,我添加了代码"检查自我缩减"这会改变输出:"等于"总是打印两次。控制台有时会打印8,有时打印10。

我的理解是reduce()是并行foldr / foldl的Java实现。 reduce()的第三个参数,combiner用于合并缩减的并行执行结果。是对的吗?如果是这样,为什么减少的结果需要与自身结合?此外,如何修复此代码以使其提供正确的输出并仍然并行运行?

编辑: 请忽略我没有使用方法参考来简化代码的事实,因为我的最终目标是通过向Foo添加更多字段来压缩。

1 个答案:

答案 0 :(得分:0)

我认为问题在于"身份" Foo被重用了太多。

以下是一项修改,其中每个Foo都有自己的ID号,以便我们跟踪它:

class Foo {
    private static int currId = 0;
    private static Object lock = new Object();
    int id;
    int len;
    public Foo() {
        synchronized(lock) {
            id = currId++;
        }
    }    
}

public class Main {
    public static void main(String[] args) throws Exception{
    System.out.println(Stream.of("alpha", "beta", "gamma", "delta").parallel().reduce(
            new Foo(),
            (f, s) -> {
                System.out.println("Adding to #" + f.id + ": " +
                     f.len + " + " + s.length() + " => " + (f.len+s.length())); 
                f.len += s.length(); return f; },
            (f1, f2) -> {
                Foo f = new Foo();
                f.len = f1.len + f2.len;
                System.out.println("Creating new #" + f.id + " from #" + f1.id + " and #" + f2.id + ": " +
                    f1.len + " + " + f2.len + " => " + (f1.len+f2.len));
                return f;
            }
    ).len);
}

我得到的输出是:

Adding to #0: 0 + 5 => 5
Adding to #0: 0 + 4 => 4
Adding to #0: 5 + 5 => 10
Adding to #0: 9 + 5 => 14
Creating new #2 from #0 and #0: 19 + 19 => 38
Creating new #1 from #0 and #0: 14 + 14 => 28
Creating new #3 from #2 and #1: 38 + 28 => 66
66

每次都不一致。我注意到的是,每次你说f.len += s.length()时,它都会添加到相同的 Foo,这意味着第一个new Foo()只执行一次,和长度不断添加到其中,以便相同的输入字符串'长度计数多次。由于显然有多个并行线程同时访问它,上面的结果有点奇怪,并且从一次运行变为运行。