为何Java串行代码执行耗时反而比并行代码更短?
问题分析与解决方案
你的并行求和代码没达到预期加速比反而更慢,核心原因集中在这几点:
1. 计算任务太小,并行开销远超收益
你只用了10个元素的数组,串行求和的计算量微乎其微,但并行模式下要创建任务对象、调度线程、执行fork()/join()操作,这些额外开销远大于计算本身,自然比串行慢。
2. 单次测试误差过大
单次计时受JVM即时编译、系统进程调度等因素影响极大,一次结果无法反映真实性能。
3. 缺少JVM预热
刚启动的JVM还没对代码进行即时编译(JIT),第一次运行的速度不能代表稳定状态下的性能。
具体优化方案
方案一:放大计算规模
把数组大小调整到100万甚至1000万,让计算量足以覆盖并行调度的开销。
方案二:多次测试取平均值
重复运行多次计算,跳过第一次的预热结果,取后续运行的平均时间,消除偶然误差。
方案三:改用RecursiveTask规范实现
RecursiveAction是无返回值任务,求和这类有返回值的场景更适合用RecursiveTask,无需手动维护result变量,符合Fork/Join框架设计规范。
方案四:设置并行阈值
当任务拆分到足够小时(比如数组长度小于1000),直接切换为串行计算,避免过度拆分带来的额外开销。
修改后的示例代码
import java.util.Arrays; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; public class TestProgram { public static double addSequential(double[] values) { double sum = 0; for (double value : values) { sum += value; } return sum; } public static double addParallel(double[] values) { ForkJoinPool pool = ForkJoinPool.commonPool(); return pool.invoke(new SumArrayTask(0, values.length, values)); } public static void main(String[] args) { // 放大数组规模到1000万 int size = 10_000_000; double[] arr = new double[size]; for (int i = 0; i < size; i++) { arr[i] = Math.floor(100 * Math.random()); } // JVM预热:先跑几次计算,触发JIT编译 for (int i = 0; i < 5; i++) { addSequential(arr); addParallel(arr); } // 串行测试:重复5次取平均 long sequentialTotal = 0; for (int i = 0; i < 5; i++) { long start = System.nanoTime(); double seqResult = addSequential(arr); sequentialTotal += System.nanoTime() - start; if (i == 0) System.out.println("Result (sequential): " + seqResult); } long sequentialAvg = sequentialTotal / 5; System.out.println("Average Sequential Time: " + sequentialAvg + " ns"); // 并行测试:重复5次取平均 long parallelTotal = 0; for (int i = 0; i < 5; i++) { long start = System.nanoTime(); double parResult = addParallel(arr); parallelTotal += System.nanoTime() - start; if (i == 0) System.out.println("Result (parallel): " + parResult); } long parallelAvg = parallelTotal / 5; System.out.println("Average Parallel Time: " + parallelAvg + " ns"); System.out.println("Speedup: " + (double) sequentialAvg / parallelAvg); } static class SumArrayTask extends RecursiveTask<Double> { // 阈值:任务小于该规模时直接串行计算 private static final int THRESHOLD = 1000; private int start; private int end; private double[] data; public SumArrayTask(int startIndex, int endIndex, double[] arr) { this.start = startIndex; this.end = endIndex; this.data = arr; } @Override protected Double compute() { int length = end - start; if (length <= THRESHOLD) { double sum = 0; for (int i = start; i < end; i++) { sum += data[i]; } return sum; } // 拆分任务并行执行 int mid = start + length / 2; SumArrayTask left = new SumArrayTask(start, mid, data); SumArrayTask right = new SumArrayTask(mid, end, data); left.fork(); double rightResult = right.compute(); double leftResult = left.join(); return leftResult + rightResult; } } }
优化效果说明
- 放大数组规模后,计算量成为性能主导,并行调度的开销占比大幅降低,在你的四核i7设备上,应该能看到接近2-3倍的加速比。
- 预热和多次取平均消除了JIT编译、系统调度带来的偶然误差,结果更真实可靠。
- 阈值判断避免了过度拆分任务,进一步减少了并行的额外开销。
内容的提问来源于stack exchange,提问作者noblecaden
相关产品推荐
相关产品推荐

