累加计算必须用RecursiveTask,因其返回结果;RecursiveAction无返回值,无法获取累加结果。阈值宜设1000~5000,优先用commonPool(),避免subList并发问题。

RecursiveTask vs RecursiveAction:选错就白忙活
累加计算必须用 RecursiveTask,不是因为“看起来更高级”,而是它返回结果——RecursiveAction 不返回任何值,强行在 compute() 里写 return sum 会编译失败。常见错误是照着并行遍历例子抄,把累加逻辑塞进 RecursiveAction,最后发现主线程根本拿不到结果。
-
RecursiveTask<Long>:适用于需要汇总子任务结果的场景(如求和、最大值、计数) -
RecursiveAction:只做副作用操作,比如批量更新数据库、写文件、打日志 - 泛型类型必须和
compute()返回值严格一致,return (int)sum配RecursiveTask<Long>会触发类型擦除陷阱,运行时可能抛ClassCastException
拆分阈值(threshold)设多少才不拖慢性能
阈值不是越大越好,也不是越小越好。设成 1 意味着每个数字都新建一个子任务,线程调度开销压倒计算收益;设成 100 万又退化成单线程遍历。实测在 JDK 8–17、普通服务器上,对纯数值累加,阈值取 1000 ~ 5000 区间最稳。
- 数据局部性好(如数组连续内存)→ 阈值可稍大(
5000) - 数据分散或含对象引用(如
List<BigDecimal>)→ 阈值建议1000起步 - 用
ForkJoinPool.commonPool().getParallelism()查当前并行度,阈值应 ≈ 总数据量 ÷ 并行度 × 2~3,避免大量空闲线程等任务
别直接 new ForkJoinPool():commonPool 够用且省心
90% 的累加场景用 ForkJoinPool.commonPool() 就行。自己 new ForkJoinPool(4) 看似可控,实际容易踩两个坑:一是忘记 shutdown() 导致线程泄漏;二是并行度设错,比如设成 1 却以为能并发。
- 除非要隔离任务(比如后台统计不能影响 HTTP 请求线程池),否则不要自建池
- 修改 commonPool 并行度要用系统属性:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=8,代码里调setParallelism()无效 - 用
invoke()启动任务,别用fork()/join()手动管理,后者易漏join()导致结果丢失
数组切片别用 subList:ArrayList.subList() 是假切片
对 ArrayList 调 subList(start, end) 返回的是视图,底层仍指向原数组——多个子任务并发修改会冲突;更糟的是,如果原列表后续被扩容,所有 subList 实例瞬间失效,抛 ConcurrentModificationException。
立即学习“Java免费学习笔记(深入)”;
- 正确做法:传原始数组 +
left/right下标,累加时用for (int i = left; i - 若必须用集合,先转成数组:
Long[] arr = list.toArray(new Long[0]),再按索引切 - 千万别在
compute()里反复调list.size()——它可能被其他线程改,结果非预期
递归拆分本身不难,难的是边界判断和数据结构选择。数组下标越界、共享集合被并发修改、阈值误设成常量 1——这些地方一松懈,跑出来的结果要么错,要么比单线程还慢。










