如何优化Java中平方根与立方根差值累加计算的性能?
优化计算密集型分治任务:用ForkJoinPool和并行流提速你的数值累加
嘿,你的思路完全没错!ForkJoinPool和并行流确实能帮你进一步榨干多核CPU的性能,尤其是这种计算密集型的分治任务。先给你指个小细节:你代码里的calculatorService构造函数写成了SqrtSummer,这是个笔误,得修正一下。接下来我给你具体演示两种优化方案:
一、用ForkJoinPool实现精细化分治
ForkJoinPool天生就是为分治任务设计的,它会自动帮你拆分任务、调度线程,比固定线程池更灵活,能减少任务调度的开销。你需要实现一个RecursiveTask来定义你的计算单元:
import java.text.DecimalFormat; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; public class ForkJoinCalculator { private static final int TARGET_NUM = Integer.MAX_VALUE; // 拆分阈值:当区间大小小于这个值时,直接计算,避免过度拆分带来的开销 private static final int SPLIT_THRESHOLD = 100000; public static void main(String[] args) { DecimalFormat df2 = new DecimalFormat(".##"); long startTime = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); // 提交根任务:计算0到TARGET_NUM的总和 Double total = forkJoinPool.invoke(new SqrtCbrtSumTask(0, TARGET_NUM)); System.out.println("Result= " + df2.format(total) + "\nTime passed= " + ((System.currentTimeMillis() - startTime)/1000f)); forkJoinPool.shutdown(); } static class SqrtCbrtSumTask extends RecursiveTask<Double> { private final int start; private final int end; public SqrtCbrtSumTask(int start, int end) { this.start = start; this.end = end; } @Override protected Double compute() { // 如果区间足够小,直接计算 if (end - start <= SPLIT_THRESHOLD) { double sum = 0; for (int i = start; i <= end; i++) { sum += Math.sqrt(i) - Math.cbrt(i); } return sum; } else { // 拆分任务:分成左右两个子任务 int mid = (start + end) / 2; SqrtCbrtSumTask leftTask = new SqrtCbrtSumTask(start, mid); SqrtCbrtSumTask rightTask = new SqrtCbrtSumTask(mid + 1, end); // 执行子任务 invokeAll(leftTask, rightTask); // 合并结果 return leftTask.join() + rightTask.join(); } } } }
关键点说明:
- 拆分阈值:我设了
100000,你可以根据自己的CPU核心数调整——太小会导致任务调度过多,太大则无法充分利用并行能力。 - ForkJoinPool会自动管理线程,不需要你手动拆分区间给固定数量的线程,它会根据系统负载动态调整任务分配。
二、用并行流实现极简版本
如果你不想写复杂的ForkJoin任务,Java 8+的并行流能以极简的代码实现并行计算,底层其实也是用ForkJoinPool:
import java.text.DecimalFormat; import java.util.stream.IntStream; public class ParallelStreamCalculator { private static final int TARGET_NUM = Integer.MAX_VALUE; public static void main(String[] args) { DecimalFormat df2 = new DecimalFormat(".##"); long startTime = System.currentTimeMillis(); // 用并行流直接计算总和 double total = IntStream.rangeClosed(0, TARGET_NUM) .parallel() .mapToDouble(i -> Math.sqrt(i) - Math.cbrt(i)) .sum(); System.out.println("Result= " + df2.format(total) + "\nTime passed= " + ((System.currentTimeMillis() - startTime)/1000f)); } }
进阶优化:自定义并行流的线程池
默认的并行流用的是公共ForkJoinPool,如果你想控制线程数量,可以自定义线程池:
import java.text.DecimalFormat; import java.util.concurrent.ForkJoinPool; import java.util.stream.IntStream; public class CustomPoolParallelStream { private static final int TARGET_NUM = Integer.MAX_VALUE; private static final int THREAD_COUNT = 8; public static void main(String[] args) { DecimalFormat df2 = new DecimalFormat(".##"); long startTime = System.currentTimeMillis(); ForkJoinPool customPool = new ForkJoinPool(THREAD_COUNT); double total = customPool.submit(() -> IntStream.rangeClosed(0, TARGET_NUM) .parallel() .mapToDouble(i -> Math.sqrt(i) - Math.cbrt(i)) .sum() ).join(); System.out.println("Result= " + df2.format(total) + "\nTime passed= " + ((System.currentTimeMillis() - startTime)/1000f)); customPool.shutdown(); } }
为什么这两种方案比你原来的FixedThreadPool更快?
- 任务调度更高效:ForkJoinPool用的是工作窃取算法,空闲线程会主动从忙碌线程的任务队列里拿任务执行,减少线程 idle 的时间。
- 动态任务拆分:不需要你手动拆分固定区间给线程,ForkJoin会根据实际计算情况拆分任务,让每个线程的负载更均衡。
- 并行流的极简封装:底层优化了很多细节,比如避免了Future的手动等待和汇总,代码更简洁且性能优异。
你可以自己测试一下这几种方案的耗时,应该会比你现有的8线程版本更快~
内容的提问来源于stack exchange,提问作者Mert Serimer
相关产品推荐
相关产品推荐

