1. 核心概念简述
Fork/Join 框架是 Java 7 引入的一个用于并行执行任务的框架,其核心思想是分治法(Divide and Conquer):
- Fork(分解):将一个大任务拆分成若干个互不依赖的小任务。如果小任务还不够小,就继续拆分,直到达到阈值。
- Join(合并):执行这些小任务,并将它们的结果递归合并,最终得到大任务的结果。
它主要用于加速CPU 密集型任务的计算,充分利用多核处理器的优势。
2. 原理与源码关键点
Fork/Join 框架主要包含两个核心组件:ForkJoinPool(线程池)和 ForkJoinTask(任务)。
工作窃取算法 (Work-Stealing Algorithm) —— 核心考点
这是 Fork/Join 框架性能高效的关键。
- 双端队列:池中的每个工作线程(Worker Thread)都有自己的双端队列(Deque)。
- LIFO(后进先出):线程自己产生的任务放在队头,自己取任务也从队头取(类似栈,利用 CPU 缓存局部性)。
- Stealing(窃取):当一个线程完成了自己队列中的所有任务,它不会闲着,而是随机从其他繁忙线程的队列队尾(Tail)“窃取”一个任务来执行。
- 优势:充分利用了所有线程的算力,减少了线程间的竞争(自己取队头,别人偷队尾)。
核心类
RecursiveTask<V>:有返回值的任务。RecursiveAction:无返回值的任务。
3. 性能与场景考量
- 适用场景:
- CPU 密集型:如大规模数组排序(Arrays.parallelSort)、图像处理、复杂数据计算。
- 不适合 I/O 密集型:因为工作线程数通常默认为 CPU 核心数,一旦阻塞,整个池的效率会大打折扣。
- Parallel Stream:
- Java 8 的
parallelStream()底层默认使用的就是ForkJoinPool.commonPool()。 - 注意:千万不要在并行流中执行阻塞 I/O 操作,否则会拖垮整个系统的公共线程池,影响其他业务。
- Java 8 的
4. 总结与示例
回答总结: “Fork/Join 是基于分治思想的并行计算框架,核心在于工作窃取算法。每个线程维护私有双端队列,空闲线程会从其他线程队列的尾部窃取任务,从而实现负载均衡。它非常适合处理大规模的 CPU 密集型任务,也是 Java 8 并行流的底层实现。”
代码示例(求和):
public class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000; // 拆分阈值
private long start, end;
public SumTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 1. 任务足够小,直接计算
if (end - start <= THRESHOLD) {
long sum = 0;
for (long i = start; i <= end; i++) sum += i;
return sum;
}
// 2. 任务太大,Fork 拆分
long mid = (start + end) / 2;
SumTask leftTask = new SumTask(start, mid);
SumTask rightTask = new SumTask(mid + 1, end);
// 异步执行子任务
leftTask.fork();
rightTask.fork();
// 3. Join 合并结果
return leftTask.join() + rightTask.join();
}
}
// 调用
ForkJoinPool pool = new ForkJoinPool();
Long result = pool.invoke(new SumTask(1, 100000));