当前位置: 首页 > 知识库问答 >
问题:

Java流减少无法解释的行为

张逸清
2023-03-14

有人能给我指出正确的方向吗,因为我不明白这个问题。

我正在执行以下方法

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,a,null,a]

我不明白为什么它在结果列表中放了两个空。我期望答案是

[a,b]

由于它是一个并行流,因此要减少的第一个参数

新建ArrayList()

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

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

有趣的是,如果我在累加器中放入一个print语句来打印输入值,输出就会改变。所以接下来

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);

}

此输出中的结果

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

有人能解释一下吗?

共有3个答案

潘智刚
2023-03-14

它相当简单,第一个参数是标识,或者我会说从零开始。对于并行流使用,此值被重用。这意味着并发问题(添加中的空值)和重复。

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

    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对于身份提供函数没有重载。原因是这里应该使用收集。

冷善
2023-03-14

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

这就是你错的地方。第一个参数是单个ArrayList实例,而不是lambda表达式可以生成多个ArrayList实例。

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

您的组合器实际上将列表中的所有元素添加到同一个列表中。

如果累加器和组合器都将生成新的数组列表,而不是改变其输入,则可以获得预期的输出:

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;
        }
);

也就是说,您根本不应该使用减少收集是执行可变减少的正确方法:

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

如您所见,在这里,与减少不同,您传递的第一个参数是供应商

魏泰
2023-03-14

您应该使用Collections.synchronizedList(),因为ArrayList不是线程安全的,并发访问时会出现意外行为,就像您使用并行流()一样。

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

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);
}

输出:

有时您会得到以下输出:

[a, b]

有时这个:

[b, a]

原因是它是一个并行流(),所以你不能确定执行的顺序。

 类似资料:
  • 我在一个应用程序里工作。这个应用程序在Android7.x.x中运行,但当我尝试在Android5.x.x中运行这个应用程序时,这个应用程序崩溃了。我认为这是因为是API25。当我尝试将其更改为API21(Android5)时,我出现了一些错误。我可以在Android5中对我的应用工作做些什么? PS:我不知道这款应用在Android6中是否有效,但很可能是不行的。 Build.Gradle: 执

  • 问题内容: 背景 我有一个Spring批处理程序,该程序读取一个文件(我正在使用的示例文件的大小约为4 GB),对该文件进行少量处理,然后将其写到Oracle数据库中。 我的程序使用1个线程读取文件,并使用12个工作线程进行处理和数据库推送。 我正在搅动很多年轻一代的记忆,这使我的程序运行得比我想象的要慢。 建立 JDK 1.6.18 春季批处理2.1.x 4核计算机,带16 GB内存 问题 使用

  • 什么时候使用和?有没有人有好的、具体的例子来说明什么时候走一条路或者走另一条路更好呢? Javadoc提到collect()是一个可变约简。 以上的说法是猜测,然而,我希望一个专家在这里插话。

  • 我有以下形式的地图: 让INNER成为内部地图,即。 例如,我想在一个新的中减少START映射 它们具有相同的键,但值不同。特别是,对于每个键,我希望新的Double值是相应键的INNER映射中值的SUM。 如何使用JAVA 8的流API来实现这一点? 谢谢大家。 编辑:样例地图为 我想要一张像下面这样的新地图:

  • 问题内容: 假设我有一个布尔值流,而我正在编写的reduce操作是||(OR)。我是否可以这样编写它:如果true遇到值,则放弃对至少某些元素的求值? 我正在寻找某种程度的优化(也许是并行流),不一定要完全优化,尽管后者会很棒。 问题答案: 我怀疑您想要这种构造。 你可以看一下 Stream.of(1, 2, 3, 4).peek(System.out::println).anyMatch(i -