线程池-ForkJoinPool
Fork/Join
Fork/Join是Java 7引入的一个并行计算框架,专门用于高效处理可以递归分解的任务。它是ExecutorService接口的一个实现,采用了工作窃取(work-stealing)算法来提高多核CPU的利用率。
核心思想
- 分而治之(Divide and Conquer):将大任务拆分成小任务并行处理
- 工作窃取(Work-Stealing):空闲线程可以从其他线程的任务队列尾部"偷取"任务执行
主要组件
1. ForkJoinPool
- 特殊的线程池实现
- 默认线程数等于处理器核心数(Runtime.getRuntime().availableProcessors())
- 每个线程维护自己的双端任务队列
2. ForkJoinTask
- 表示可并行执行的任务
- 两个主要子类:
- RecursiveAction:用于没有返回值的任务
- RecursiveTask:用于有返回值的任务
基本使用模式
1. 继承RecursiveAction(无返回值)
class MyAction extends RecursiveAction {
@Override
protected void compute() {
if (任务足够小) {
// 直接执行任务
} else {
// 拆分任务
MyAction left = new MyAction(...);
MyAction right = new MyAction(...);
// 并行执行子任务
invokeAll(left, right);
}
}
}
2. 继承RecursiveTask(有返回值)
class MyTask extends RecursiveTask<ResultType> {
@Override
protected ResultType compute() {
if (任务足够小) {
// 直接计算结果并返回
return result;
} else {
// 拆分任务
MyTask left = new MyTask(...);
MyTask right = new MyTask(...);
// 并行执行子任务
left.fork();
right.fork();
// 合并结果
return combine(left.join(), right.join());
}
}
}
工作窃取算法原理
- 每个工作线程维护一个双端队列
- 线程从自己队列的头部获取任务执行
- 当自己队列为空时,从其他线程队列的尾部"偷取"任务
- 减少了线程间的竞争,提高了CPU利用率
使用示例
示例1:计算1到n的和
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000; // 阈值
private final int[] array;
private final int start;
private final int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
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 += array[i];
}
return sum;
} else {
// 大任务拆分
int middle = (start + end) / 2;
SumTask left = new SumTask(array, start, middle);
SumTask right = new SumTask(array, middle, end);
// 并行执行
left.fork();
right.fork();
// 合并结果
return left.join() + right.join();
}
}
}
// 使用方式
int[] array = new int[10000];
// 初始化数组...
ForkJoinPool pool = new ForkJoinPool();
long sum = pool.invoke(new SumTask(array, 0, array.length));
示例2:并行排序
class SortTask extends RecursiveAction {
private static final int THRESHOLD = 1000;
private final int[] array;
private final int start;
private final int end;
public SortTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
// 小数组直接排序
Arrays.sort(array, start, end);
} else {
// 拆分数组
int middle = (start + end) / 2;
SortTask left = new SortTask(array, start, middle);
SortTask right = new SortTask(array, middle, end);
// 并行排序
invokeAll(left, right);
// 合并已排序的子数组
merge(array, start, middle, end);
}
}
private void merge(int[] array, int start, int middle, int end) {
// 合并逻辑...
}
}
关键特点
- 高效利用多核CPU:特别适合计算密集型任务
- 自动负载均衡:工作窃取机制使线程保持忙碌状态
- 减少线程竞争:每个线程有自己的任务队列
- 递归任务分解:天然适合分治算法
适用场景
- 大规模数据处理
- 递归算法实现(如快速排序、归并排序)
- 数学计算(如矩阵运算)
- 图像处理
- 任何可以分解为独立子任务的问题
注意事项
- 任务拆分不宜过细:任务太小会增加调度开销
- 避免阻塞操作:不适合IO密集型任务
- 合理设置阈值:根据任务特性调整任务拆分阈值
- 避免共享可变状态:任务间应尽量减少共享数据
- join()应在fork()之后调用:确保正确的任务执行顺序
性能调优
-
调整ForkJoinPool的并行级别(线程数)
ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * 2); -
根据任务特性选择合适的阈值
-
考虑使用带async模式的方法(fork()而不是invokeAll())
-
对于复杂任务,考虑使用Phaser等同步工具
Fork/Join框架是Java并发编程中处理可分解任务的强大工具,正确使用可以显著提高多核环境下的程序性能。

浙公网安备 33010602011771号