ForkJoinPool
总结
一个特殊线程池,它专门用于执行 ForkJoinTask;
设计目的是为了更有效地处理 可以被分解成更小任务 的工作负载,这些任务可以通过递归地分叉(fork)和合并(join)来并行执行。
这种模式非常适合于分治算法,比如快速排序、矩阵乘法等。
特性
- 工作窃取:
ForkJoinPool
使用了一种称为“工作窃取”的算法,其中 空闲的线程 可以 从其他忙于处理任务的线程 那里“窃取”待处理的任务。这有助于保持所有线程忙碌,从而提高整体效率。 - 轻量级子任务:
ForkJoinTask
通常代表一个轻量级的子任务,这样可以减少任务切换的开销。 - 动态调整:
ForkJoinPool
可以根据需要动态调整其大小,但通常建议使用默认设置或显式指定大小。
创建 ForkJoinPool
- 默认构造函数会创建一个与系统可用处理器数量相匹配的线程数的池。
- 也可以通过传递自定义参数来创建
ForkJoinPool
,例如指定线程数、异常处理器等。
ForkJoinTask
ForkJoinTask
是 ForkJoinPool
中执行任务的抽象类,有两个主要的实现:
RecursiveAction
:用于 没有返回结果 的任务。RecursiveTask<V>
:用于 有返回结果 的任务,这里的V
表示返回值类型。
示例
public class SumCalculator extends RecursiveTask<Long> { private final long[] numbers; private final int start; private final int end; private static final int THRESHOLD = 1000; // 阈值,决定何时停止拆分子任务 public SumCalculator(long[] numbers, int start, int end) { this.numbers = numbers; 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 += numbers[i]; } return sum; } else { // 否则将任务拆分成两个更小的任务 int middle = (start + end) / 2; SumCalculator leftTask = new SumCalculator(numbers, start, middle); SumCalculator rightTask = new SumCalculator(numbers, middle, end); // 异步执行左边的任务 leftTask.fork(); // 计算右边的任务 long rightResult = rightTask.compute(); // 获取左边任务的结果 long leftResult = leftTask.join(); // 返回左右两边任务的结果之和 return leftResult + rightResult; } } public static void main(String[] args) { long[] numbers = new long[100000000]; for (int i = 0; i < numbers.length; i++) { numbers[i] = i; } ForkJoinPool pool = new ForkJoinPool(); SumCalculator task = new SumCalculator(numbers, 0, numbers.length); long start = System.currentTimeMillis(); long result = pool.invoke(task); // 提交任务并等待完成 System.out.println("The sum is: " + result + " cost:" + (System.currentTimeMillis() - start)); long start1 = System.currentTimeMillis(); long sum = 0; for (int i = 0; i < numbers.length; i++) { sum += numbers[i]; } System.out.println("sum: " + sum + " cost: " + (System.currentTimeMillis() - start1)); } }