ForkJoinTask是Java并发包java.util.concurrent中提供的抽象类,它的核心设计思路是分治思想,把一个大任务拆分成多个小的子任务并行执行,最后把子任务的结果合并得到最终结果,适合处理可以递归拆分的任务场景。

ForkJoinTask核心概念
ForkJoinTask有两个常用的子类,分别是RecursiveAction和RecursiveTask,两者的区别是RecursiveAction没有返回值,适合不需要返回结果的任务,RecursiveTask有返回值,适合需要汇总结果的任务。
使用ForkJoinTask需要配合ForkJoinPool线程池,它是专门用来执行ForkJoinTask的线程池,默认会创建和CPU核心数相同的工作线程,减少线程上下文切换的开销。
任务拆分的核心步骤
使用ForkJoinTask拆分并行任务一般遵循以下流程:
- 自定义类继承RecursiveAction或者RecursiveTask,根据任务是否需要返回值选择对应的父类
- 重写
compute方法,在这个方法里实现任务拆分和结果合并的逻辑 - 在compute方法中判断当前任务的大小,如果任务足够小就直接执行,否则拆分成多个子任务
- 把子任务通过
fork方法提交到线程池执行,通过join方法获取子任务的执行结果 - 创建ForkJoinPool实例,把最外层的任务提交到线程池执行,获取最终结果
无返回值任务拆分示例
下面以打印1到100的数字为例,演示RecursiveAction的使用,当任务范围大于10的时候就拆分成两个子任务:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
// 自定义无返回值任务类
class PrintTask extends RecursiveAction {
// 任务的起始值
private int start;
// 任务的结束值
private int end;
// 任务拆分阈值,小于这个值的任务直接执行
private static final int THRESHOLD = 10;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
// 如果任务范围小于等于阈值,直接执行打印逻辑
if (end - start <= THRESHOLD) {
for (int i = start; i <= end; i++) {
System.out.println(Thread.currentThread().getName() + " 打印数字:" + i);
}
} else {
// 任务过大,拆分成两个子任务
int mid = (start + end) / 2;
PrintTask leftTask = new PrintTask(start, mid);
PrintTask rightTask = new PrintTask(mid + 1, end);
// 提交子任务执行
leftTask.fork();
rightTask.fork();
// 等待子任务执行完成,无返回值所以不需要处理join的结果
leftTask.join();
rightTask.join();
}
}
}
public class ForkJoinActionDemo {
public static void main(String[] args) {
// 创建ForkJoinPool线程池
ForkJoinPool pool = new ForkJoinPool();
// 创建最外层的任务,打印1到100
PrintTask task = new PrintTask(1, 100);
// 提交任务到线程池执行
pool.invoke(task);
// 关闭线程池
pool.shutdown();
}
}
有返回值任务拆分示例
下面以计算1到100的累加和为例,演示RecursiveTask的使用,当任务范围大于10的时候就拆分成两个子任务,最后合并子任务的结果:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
// 自定义有返回值任务类
class SumTask extends RecursiveTask<Long> {
// 任务的起始值
private int start;
// 任务的结束值
private int end;
// 任务拆分阈值
private static final int THRESHOLD = 10;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 如果任务范围小于等于阈值,直接计算返回结果
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
// 任务过大,拆分成两个子任务
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(start, mid);
SumTask rightTask = new SumTask(mid + 1, end);
// 提交子任务执行
leftTask.fork();
rightTask.fork();
// 获取子任务的执行结果并合并
long leftResult = leftTask.join();
long rightResult = rightTask.join();
return leftResult + rightResult;
}
}
}
public class ForkJoinTaskDemo {
public static void main(String[] args) {
// 创建ForkJoinPool线程池
ForkJoinPool pool = new ForkJoinPool();
// 创建最外层的任务,计算1到100的和
SumTask task = new SumTask(1, 100);
// 提交任务到线程池并获取结果
long result = pool.invoke(task);
System.out.println("1到100的累加和为:" + result);
// 关闭线程池
pool.shutdown();
}
}
使用注意事项
在使用ForkJoinTask的时候需要注意几个问题:
- 任务拆分的阈值需要根据实际任务场景调整,阈值太小会导致创建过多的子任务,增加线程调度开销,阈值太大会导致并行度不够,无法充分利用CPU资源
- 不要在compute方法里做过多的阻塞操作,否则会影响ForkJoinPool的工作线程效率,因为ForkJoinPool的工作线程数量默认和CPU核心数一致,阻塞操作会导致线程无法处理其他任务
- fork方法提交子任务之后,尽量先执行其他逻辑再调用join方法,或者可以使用
invokeAll方法批量提交子任务,invokeAll方法会优化子任务的执行顺序,提升执行效率 - RecursiveTask的compute方法返回的是泛型类型,需要注意返回值的类型匹配,避免类型转换错误
适用场景说明
ForkJoinTask适合处理可以递归拆分的CPU密集型任务,比如大数组排序、大批量数据计算、递归遍历等场景。如果是IO密集型任务,比如网络请求、文件读写,不建议使用ForkJoinTask,因为IO操作会阻塞工作线程,更适合使用普通的线程池来处理。
ForkJoinTaskJava并行任务任务拆分ForkJoinPool修改时间:2026-07-21 18:39:26