并行流需要combiner参数是因为数据被分片后各线程独立计算局部结果,必须显式合并;combiner须满足结合律和线程安全,如list合并需创建新容器而非修改原对象。

Stream.reduce() 的第三个参数(即 combiner)在并行流中至关重要——它负责将不同线程产生的部分结果合并成最终结果。若忽略其正确性,会导致数据丢失、重复计算甚至抛出异常。
为什么并行流需要 combiner 参数
并行流会把数据分片,各线程独立调用 accumulator(第二个参数)生成局部结果;这些局部结果互不共享,必须通过 combiner 显式合并。而串行流只走一条线程,combiner 不会被调用。
常见错误是把 combiner 写成和 accumulator 一样的逻辑,或忽略其**结合律**与**线程安全性**要求。
combiner 必须满足的两个核心条件
结合律(Associativity):合并顺序不影响结果。例如:
(a op b) op c == a op (b op c)
✅ 正确:+、*、Math.max、String::concat(注意:不是所有 concat 都安全,见下文)
❌ 错误:-、/、List.add(非线程安全且不满足结合律语义)
无副作用 & 线程安全:combiner 函数不能修改入参对象,也不能依赖外部状态。例如:
✅ 正确:new ArrayList(list1).addAll(list2)
❌ 错误:list1.addAll(list2) —— 修改了 list1,多个线程并发调用会破坏数据
典型场景与正确写法示例
1. 合并多个 List(收集元素)
不要直接 addAll,要创建新容器:
- accumulator: (list, item) -> { List
l = new ArrayList(list); l.add(item); return l; } - combiner: (list1, list2) -> { List
merged = new ArrayList(list1); merged.addAll(list2); return merged; }
2. 求和或拼接字符串(更推荐用 collect)
虽然 Integer::sum、String::concat 满足结合律,但 String.concat 在并行中会产生大量中间对象,性能差;建议改用 collect + StringBuilder 或 Collectors.joining()
3. 自定义对象聚合(如统计)
假设 SumCount 表示「总和 + 元素个数」:
- accumulator: (sc, x) -> new SumCount(sc.sum + x, sc.count + 1)
- combiner: (sc1, sc2) -> new SumCount(sc1.sum + sc2.sum, sc1.count + sc2.count)
关键:每次返回新对象,不复用或修改 sc1/sc2
什么时候该用 reduce 而不是 collect
reduce 更适合「纯函数式归约」,结果类型与元素类型一致或简单衍生;collect 更适合构建复杂容器(如 Map、TreeSet)、需定制并发策略(ConcurrentMap)、或利用 Collector 的特性(如 groupingBy)。如果 combiner 写起来别扭或容易出错,优先考虑 collect。











