基于Set的并行Stream的reduce()操作行为异常,求与List的差异原因
并行流reduce操作中Set与List的结果差异原因分析
先看你给出的代码和现象:
import java.util.Collection; import java.util.List; import java.util.Set; public class StreamReduce { public static void main(String[] args) { // Collection<String> l = List.of("A", "B"); Collection<String> l = Set.of("A", "B"); var r = l.parallelStream().reduce("I", (a,b)-> a + b); System.out.println(r); } }
使用Set.of("A", "B")时输出为IBIAI,换成List.of("A", "B")时输出为IAB,两者的差异可以从以下几点拆解:
1. 并行流reduce的底层逻辑
并行流执行reduce时,会把元素集合拆分成多个独立的子任务,每个子任务都会用你指定的标识元素(这里是"I")作为初始值,先完成自身的累加操作,最后再把所有子任务的结果合并成最终输出。
2. Set与List的核心差异:有序性
- List是有序集合:哪怕用并行流,元素的处理顺序、子任务的拆分与合并都会遵循原集合的顺序。对于元素量小的List,JVM甚至可能直接采用串行逻辑执行
reduce,最终结果就是"I"+"A"+"B",也就是符合预期的IAB。 - Set是无序集合:
Set.of返回的是无序实现,并行流处理时,元素的遍历顺序、子任务的拆分规则完全没有固定约束。每个子任务都会独立用"I"和元素拼接,比如得到"IA"和"IB",合并这些子结果时,顺序是随机的,甚至可能因为拆分逻辑的原因,多次引入标识元素,最终出现IBIAI这类包含多个"I"且位置不确定的结果。
3. 更本质的问题:违反了并行reduce的要求
Java并行流的reduce操作有两个硬性要求,你的代码刚好都违反了:
- 标识元素必须是幺元:也就是
identity op a和a op identity的结果都必须等于a。但你的"I"显然不满足——"I"+"A"是"IA","A"+"I"是"AI",都不等于"A"。 - 累加器函数的结合性依赖幺元生效:虽然字符串拼接本身是结合性的(
(a+b)+c = a+(b+c)),但因为标识元素不是幺元,加上Set的无序性,最终结果完全不可控。
简单来说:List的结果只是刚好符合你的预期,本质是依赖了JVM的实现细节;而Set的结果暴露了你的代码不符合并行reduce规范的问题。
内容的提问来源于stack exchange,提问作者Barat Sahdzijeu
相关产品推荐
相关产品推荐

