You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

并行Stream API中非volatile容器为何能保证线程安全?

为什么并行Stream中summingInt用普通int数组做累加是线程安全的

以下是JDK Stream API内置的summingInt收集器核心实现:

public static <T> Collector<T, ?, Integer>
summingInt(ToIntFunction<? super T> mapper) {
    return new CollectorImpl<>(
            () -> new int[1],
            (a, t) -> { a[0] += mapper.applyAsInt(t); },
            (a, b) -> { a[0] += b[0]; return a; },
            a -> a[0], CH_NOID);
}

按照常规JMM认知,普通非volatile变量跨线程读取可能看不到最新值,很多人会疑惑这里为什么不用AtomicIntegerArray这类并发结构,核心原因是并行流的执行框架本身已经提供了完备的内存可见性和线程安全保障,收集器自身不需要额外做同步处理,具体可以拆成三个阶段理解:

  • 累加阶段完全线程封闭,无共享
    每个并行拆分出去的子任务,会独立调用supplier创建自己专属的int[1]累加容器,在整个子任务遍历元素、执行累加逻辑的过程中,这个数组的引用不会被发布到其他线程,始终只有当前子任务的工作线程会读写数组元素,属于纯单线程操作,不存在任何并发问题。
  • 结果交接时自带JMM happens-before 保证
    并行流底层基于ForkJoin框架实现,框架规范明确要求:子任务执行完成后,调用join()等待子任务结果的线程,一定能观测到子任务执行过程中所有的写入操作——这个内存可见性保证和volatile写入、锁释放的语义是完全一致的。
    也就是说,当工作线程把自己累加完的int[1]结果交给上层合并线程时,合并线程读到的a[0]一定是子线程写入的最新值,不会出现读到缓存旧值的问题。
  • 合并阶段无并发写入
    所有子任务的结果是按照任务拆分的树结构逐层向上合并的,执行合并逻辑时,被合并的两个容器一个是当前线程自己持有的承接结果的容器,另一个是已经执行完毕的子任务的最终结果,不会出现多个线程同时修改同一个数组元素的情况,自然不需要原子类的CAS操作来保障写原子性。

这里如果换成AtomicInteger或者其他并发容器反而是画蛇添足,会额外增加volatile读写、CAS的性能开销,在已经没有数据竞争的场景下没有任何实际收益。

内容的提问来源于stack exchange,提问作者aston_loq32

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 06:18:09