Fork/Join框架:分治法的完美实践🌲

26 阅读2分钟

大任务拆成小任务,递归执行,工作窃取,性能爆表!

一、核心思想

大任务
  ↓ fork
任务1 + 任务2fork
任务1.1 + 任务1.2 + 任务2.1 + 任务2.2
  ↓ compute
结果1 + 结果2 + 结果3 + 结果4join
最终结果

二、基本使用

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:固定线程,适合独立任务

下一篇→ 手写对象池🏊