大任务拆成小任务,递归执行,工作窃取,性能爆表!
一、核心思想
大任务
↓ fork
任务1 + 任务2
↓ fork
任务1.1 + 任务1.2 + 任务2.1 + 任务2.2
↓ compute
结果1 + 结果2 + 结果3 + 结果4
↓ join
最终结果
二、基本使用
public class SumTask extends RecursiveTask<Long> {
private final int[] array;
private final int start;
private final int end;
private static final int THRESHOLD = 1000; // 阈值
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
// 小于阈值,直接计算
if (length <= THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
// 大于阈值,拆分任务
int mid = start + length / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork(); // 异步执行左边
rightTask.fork(); // 异步执行右边
long leftResult = leftTask.join(); // 等待左边结果
long rightResult = rightTask.join(); // 等待右边结果
return leftResult + rightResult;
}
}
// 使用
ForkJoinPool pool = new ForkJoinPool();
int[] array = new int[10000000];
SumTask task = new SumTask(array, 0, array.length);
long result = pool.invoke(task);
三、任务拆分策略
阈值设置
// ❌ 太小:任务太多,开销大
private static final int THRESHOLD = 10;
// ❌ 太大:并行度不够
private static final int THRESHOLD = 1000000;
// ✅ 合适:平衡并行度和开销
private static final int THRESHOLD = 1000;
经验值
阈值 = 总数据量 / (CPU核心数 * 4)
四、工作窃取(Work Stealing)
线程1队列: [任务1, 任务2, 任务3]
线程2队列: [任务4] ← 空闲,从线程1偷任务3
线程3队列: [] ← 空闲,从线程1偷任务2
双端队列:
- 自己从头部取任务
- 别人从尾部偷任务
五、面试高频问答💯
Q: Fork/Join适合什么场景?
A:
- 大数据量计算
- 可递归拆分的任务
- CPU密集型任务
Q: 阈值如何设置?
A: 一般设置为总量 / (核心数 * 4)
Q: 和ThreadPoolExecutor的区别?
A:
- Fork/Join:工作窃取,适合递归任务
- ThreadPool:固定线程,适合独立任务
下一篇→ 手写对象池🏊