[英]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.