RecursiveTask是Java Fork/Join框架中用于有返回值分治任务的抽象类,适用于可递归拆分且子结果可合并的CPU密集型场景(如数组求和、归并排序),不适用于IO密集、强状态依赖或需频繁通信的任务。

RecursiveTask 是什么,什么时候该用它
RecursiveTask 是 Java 并 Fork/Join 框架里的一个抽象类,专为「有返回值」的分治任务设计。它不是万能并发工具——如果你的任务没有自然的拆分结构(比如数组求和、归并排序、树遍历),硬套 RecursiveTask 反而增加调度开销,还容易写错阈值逻辑。
典型适用场景:问题能递归切分成独立子问题,且子结果可合并(如 sum()、max()、countLeaves())。不适用场景:带强状态依赖、IO 主导、或子任务间需频繁通信的任务。
怎么写一个正确的 RecursiveTask 子类
核心就三点:继承 RecursiveTask<t></t>、重写 compute()、在合适时候调用 fork() 和 join()。最容易错的是「拆分阈值」和「合并时机」。
- 拆分阈值别写死成 1:对数组求和,设成
length 更合理;太小导致任务过多,太大又失去并行意义 - 必须显式调用
fork()启动子任务,不能只 new 一个然后直接compute()—— 那就退化成单线程递归 - 合并结果时,确保两个子任务都已完成:
left.fork(); Right.compute(); left.join();比left.fork(); right.fork(); left.join(); right.join();更省等待时间(避免空等)
示例:整数数组求和
class SumTask extends RecursiveTask<long> {
final int[] arr;
final int lo, hi;
static final int THRESHOLD = 8;
<pre class="brush:php;toolbar:false;">SumTask(int[] arr, int lo, int hi) {
this.arr = arr; this.lo = lo; this.hi = hi;
}
protected Long compute() {
if (hi - lo <p>}</p>
ForkJoinPool 怎么配,不配会怎样
不显式传 ForkJoinPool,invoke() 默认走公共池(ForkJoinPool.commonPool()),它的并行度是 Runtime.getRuntime().availableProcessors() - 1。这意味着:4 核机器上,最多 3 个并行线程——如果任务本身是 CPU 密集型,这刚好;但若混着阻塞操作(比如不小心在 compute() 里加了 Thread.sleep()),整个池可能卡死。
- 自定义池更可控:
new ForkJoinPool(4)显式指定并行度 - 任务抛异常时,
get()或invoke()会包装成ExecutionException,原始异常藏在 cause 里,别直接打印e.toString() - 不要在
compute()里用System.out.println()调试——ForkJoinPool 的工作线程名是ForkJoinPool-N-worker-M,输出顺序完全不可预测
为什么结果偶尔不对,或者比单线程还慢
常见原因就两个:数据竞争 + 过度拆分。
- 共享变量没隔离:比如把
sum声明成实例字段,多个compute()并发修改它——必须让每个任务管自己的局部变量 - 拆分粒度太细:阈值设成 1,100 万元素数组会生成百万级任务,线程调度和栈开销远超计算收益
- 忘了
fork():只 new 子任务并调compute(),等于手动递归,没进 ForkJoinPool 调度队列 - 用错合并顺序:比如先
join()再compute(),导致本可并行的部分被串行化
真正关键的不是“怎么写”,而是“在哪切”和“谁来等”。切的位置决定并行潜力,等的策略决定线程利用率。这两点没想清楚,代码写得再像样,也只是披着并行外衣的单线程。










