繁体   English   中英

Java Stream 减少无法解释的行为

[英]Java Stream reduce unexplained behaviour

谁能指出我正确的方向,因为我无法理解这个问题。

我正在执行以下方法。

private static void reduce_parallelStream() {
    List<String> vals = Arrays.asList("a", "b");

    List<String> join = vals.parallelStream().reduce(new ArrayList<String>(),
            (List<String> l, String v) -> {

                l.add(v);

                return l;
            }, (a, b) -> {                   
                a.addAll(b);
                return a;
            }

    );

   System.out.println(join);

}

它打印

[空,一个,null,一个]

我不明白为什么它将两个 null 放在结果列表中。 我希望答案是

[一,乙]

因为它是并行 stream 所以第一个参数要减少

新的数组列表()

对于每个输入值 a 和 b,可能会调用两次。

然后累加器 function 可能会被调用两次,因为它是一个并行流,并在每次调用中传递每个输入“a 和 b”以及种子值提供的列表。 因此 a 被添加到列表 1 中,b 被添加到列表 2 中(反之亦然)。 之后组合器将合并两个列表,但它不会发生。

有趣的是,如果我在累加器中放置一条打印语句来打印输入的值,output 会发生变化。 所以跟随

private static void reduce_parallelStream() {
    List<String> vals = Arrays.asList("a", "b");

    List<String> join = vals.parallelStream().reduce(new ArrayList<String>(),
            (List<String> l, String v) -> {
                System.out.printf("l is %s", l);
                l.add(v);
                System.out.printf("l is %s", l);
                return l;
            }, (a, b) -> {
                a.addAll(b);
                return a;
            }

    );

   System.out.println(join);

}

导致这个 output

l 是 []l 是 [b]l 是 [b, a]l 是 [b, a] [b, a, b, a]

谁能解释一下。

使用Collections.synchronizedList() parallelStream() 因为ArrayList不是线程安全的,并且在同时访问它时会出现意外行为,就像您使用parallelStream()一样。

我已经修改了您的代码,现在它可以正常工作:

private static void reduce_parallelStream() {
    List<String> vals = Arrays.asList("a", "b");

    // Use Synchronized List when with parallelStream()
    List<String> join = vals.parallelStream().reduce(Collections.synchronizedList(new ArrayList<>()),
            (l, v) -> {
                l.add(v);
                return l;
            }, (a, b) -> a // don't use addAll() here to multiplicate the output like [a, b, a, b]
    );
    System.out.println(join);
}

Output:

有时你会得到这个 output:

[a, b]

有时这个:

[b, a]

这样做的原因是它是一个parallelStream() ,所以你不能确定执行的顺序。

因为它是一个并行的 stream,所以减少new ArrayList()的第一个参数可能会为每个输入值 a 和 b 调用两次。

那就是你错了。 第一个参数是单个ArrayList实例,不是lambda 表达式可以产生多个ArrayList实例。

因此,整个缩减在单个ArrayList实例上运行。 当多个线程并行修改ArrayList时,每次执行的结果可能会发生变化。

您的combiner实际上将List List

如果accumulator和合并combiner功能都将产生新的ArrayList而不是改变其输入ArrayList ,则您可以获得预期的[a,b] output :

List<String> join = vals.parallelStream().reduce(
     new ArrayList<String>(),
        (List<String> l, String v) -> {
            List<String> cl = new ArrayList<>(l);
            cl.add(v);
            return cl;
        }, (a, b) -> {
            List<String> ca = new ArrayList<>(a);
            ca.addAll(b);
            return ca;
        }
);

也就是说,你根本不应该使用reduce collect是执行可变归约的正确方法:

List<String> join = vals.parallelStream()
                        .collect(ArrayList::new,ArrayList::add,ArrayList::addAll);

如您所见,这里与reduce不同,您传递的第一个参数是Supplier<ArrayList<String>> ,可用于根据需要生成尽可能多的中间ArrayList实例。

这很简单,第一个参数是身份,或者我会说从零开始 对于parallelStream usage ,此值被重用 这意味着并发问题(来自添加的 null)和重复。

这可以通过以下方式修补:

    final ArrayList<String> zero = new ArrayList<>();
    List<String> join = vals.parallelStream().reduce(zero,
            (List<String> l, String v) -> {
                if (l == zero) {
                    l = new ArrayList<>();
                }
                l.add(v);
                return l;
            }, (a, b) -> {
                // See comment of Holger:
                if (a == zero) return b;
                if (b == zero) return a;

                a.addAll(b);
                return a;
            }
    );

安全的。

您可能想知道为什么reduce对提供 function 的身份没有过载。 原因是这里应该使用collect

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM