Java8流顺序和并行执行产生不同的结果? [英] Java8 streams sequential and parallel execution produce different results?
问题描述
在Java8中运行以下流示例:
Running the following stream example in Java8:
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.reduce("", (s1, s2) -> s1 + "/" + s2)
);
收益率:
/a/b/c/d/e/f
这是 - 的当然 - 毫不奇怪。
由于 http:// docs .oracle.com / javase / 8 / docs / api / index.html?overview-summary.html 流是按顺序还是并行执行无关紧要:
Which is - of course - no surprise. Due to http://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html it shouldn't matter whether the stream is executed sequentially or parallel:
除了标识为显式非确定性的操作(例如findAny())之外,流是顺序执行还是并行执行不应更改计算结果。
Except for operations identified as explicitly nondeterministic, such as findAny(), whether a stream executes sequentially or in parallel should not change the result of the computation.
AFAIK reduce()
是确定性的,( s1,s2) - > s1 +/+ s2
是关联的,因此添加 parallel()
应该产生相同的结果:
AFAIK reduce()
is deterministic and (s1, s2) -> s1 + "/" + s2
is associative, so that adding parallel()
should yield the same result:
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.parallel()
.reduce("", (s1, s2) -> s1 + "/" + s2)
);
然而,我机器上的结果是:
However the result on my machine is:
/a//b//c//d//e//f
这里有什么问题?
BTW:使用(首选) .collect(Collectors.joining(/) )
而不是 reduce(...)
产生相同的结果 a / b / c / d / e / f
用于顺序执行和并行执行。
BTW: using (the preferred) .collect(Collectors.joining("/"))
instead of reduce(...)
yields the same result a/b/c/d/e/f
for sequential and parallel execution.
JVM详细信息:
java.specification.version: 1.8
java.version: 1.8.0_31
java.vm.version: 25.31-b07
java.runtime.version: 1.8.0_31-b13
推荐答案
来自reduce的文档:
From reduce's documentation:
标识值必须是累加器函数的标识。这意味着对于所有t,accumulator.apply(identity,t)等于t。
The identity value must be an identity for the accumulator function. This means that for all t, accumulator.apply(identity, t) is equal to t.
在你的情况下不是这样 - 和a创建/ a。
Which is not true in your case - "" and "a" creates "/a".
我已经提取累加器函数并添加了一个打印输出以显示发生的情况:
I have extracted the accumulator function and added a printout to show what happens:
BinaryOperator<String> accumulator = (s1, s2) -> {
System.out.println("joining \"" + s1 + "\" and \"" + s2 + "\"");
return s1 + "/" + s2;
};
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.parallel()
.reduce("", accumulator)
);
这是示例输出(运行之间不同):
This is example output (it differs between runs):
joining "" and "d"
joining "" and "f"
joining "" and "b"
joining "" and "a"
joining "" and "c"
joining "" and "e"
joining "/b" and "/c"
joining "/e" and "/f"
joining "/a" and "/b//c"
joining "/d" and "/e//f"
joining "/a//b//c" and "/d//e//f"
/a//b//c//d//e//f
你可以在你的函数中添加一个if语句来分别处理空字符串:
You can add an if statement to your function to handle empty string separately:
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.parallel()
.reduce((s1, s2) -> s1.isEmpty()? s2 : s1 + "/" + s2)
);
正如Marko Topolnik注意到的那样,检查 s2
是不需要因为累加器不必是交换函数。
As Marko Topolnik noticed, checking s2
is not required as accumulator doesn't have to be commutative function.
这篇关于Java8流顺序和并行执行产生不同的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!