Fork/Join线程池是Java 7中引入的一个用于并行执行任务的框架,它的设计目的是充分利用多核处理器的计算能力,加快处理速度,提高性能。Fork/Join框架主要用于任务需要分解为多个子任务执行的场景,是一种分而治之的并行计算模型。它的核心思想是将一个大任务分解(Fork)成若干个小任务,如果这些小任务还太大,则继续分解,直到足够小可以直接计算,然后执行这些任务,并将结果合并(Join)。
ForkJoinPool是Fork/Join框架的线程池实现,它负责执行ForkJoinTask任务。ForkJoinPool通过使用工作窃取(work-stealing)算法来优化任务的执行,即空闲的线程可以从其他线程的工作队列中偷取任务来执行,以此来减少线程间的竞争和提高效率。
ForkJoinTask是执行在ForkJoinPool中的任务的基类,有两个重要的子类:RecursiveAction和RecursiveTask。RecursiveAction代表没有返回结果的任务,而RecursiveTask代表有返回结果的任务。开发者通常需要继承这两个类之一来实现自己的并行任务。
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class SumTask extends RecursiveTask<Long> {
private final long[] numbers;
private final int start;
private final int end;
public static final long THRESHOLD = 10_000;
public SumTask(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
// 直接计算
return add(numbers, start, end);
} else {
// 继续分解任务
int split = start + length / 2;
SumTask leftTask = new SumTask(numbers, start, split);
SumTask rightTask = new SumTask(numbers, split, end);
invokeAll(leftTask, rightTask);
// 合并结果
return leftTask.join() + rightTask.join();
}
}
private long add(long[] numbers, int start, int end) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
public static void main(String[] args) {
long[] numbers = new long[20_000];
// 初始化数组...
ForkJoinTask<Long> task = new SumTask(numbers, 0, numbers.length);
Long result = new ForkJoinPool().invoke(task);
System.out.println("Sum: " + result);
}
}
这个例子中,SumTask
继承自RecursiveTask
,用于计算一个长整型数组的总和。如果任务处理的数组段足够小,它会直接计算;否则,它会将任务一分为二,递归处理。
invokeAll
方法是 ForkJoinTask
类中的一个方法,它在 ForkJoinPool
中并行执行一组任务。当有多个子任务需要并行执行,并且希望当前任务等待这些子任务全部完成时,可以使用 invokeAll
方法。
invokeAll
方法接受一个任务集合作为参数。ForkJoinPool
以并行执行。invokeAll
方法的任务)会阻塞,直到所有提交的任务都执行完成。invokeAll
方法通常在任务需要被分解成几个独立的子任务并且这些子任务可以并行处理时使用。通过并行执行这些子任务,可以有效地利用多核处理器的计算资源,从而提高应用程序的性能。
假设有一个大任务,需要被分解为两个子任务 leftTask
和 rightTask
。可以使用 invokeAll
方法来并行执行这两个子任务,并等待它们完成。
SumTask leftTask = new SumTask(numbers, start, split);
SumTask rightTask = new SumTask(numbers, split, end);
// 并行执行 leftTask 和 rightTask,并等待它们完成
invokeAll(leftTask, rightTask);
在这个示例中,invokeAll(leftTask, rightTask)
会同时执行 leftTask
和 rightTask
。当前任务(可能是一个更大的 ForkJoinTask
)会在这两个任务完成之前阻塞,确保在进行下一步操作之前,所有子任务都已完成。
invokeAll
方法时,需要考虑到任务分解的粒度。如果任务分解得太细,可能会导致大量的线程创建和上下文切换,反而降低程序的性能。invokeAll
是同步的,当前任务会等待所有子任务完成。如果需要异步执行任务,可以单独使用 fork
方法提交任务,然后在必要的时候通过 join
方法等待任务完成。invoke()
方法是 ForkJoinPool
类中的一个方法,用于在当前线程中执行给定的 ForkJoinTask
,并等待其完成,返回任务的结果。它是一种同步执行方式,即调用 invoke()
的线程会阻塞,直到提交给 ForkJoinPool
的任务完成并返回结果。
invoke()
方法并传入一个 ForkJoinTask
(如 RecursiveTask
或 RecursiveAction
)时,ForkJoinPool
会安排这个任务执行。invoke()
方法会立即开始执行任务,调用线程(即调用 invoke()
的线程)会等待任务完成。RecursiveTask
类型的任务,invoke()
方法会返回任务计算的结果。对于 RecursiveAction
类型的任务(没有返回值的任务),invoke()
方法在任务完成后返回 null
。invoke()
方法适用于需要立即执行一个任务,并且希望当前线程等待任务完成的场景。这通常用在需要并行处理的大型任务上,其中大任务被分解为多个小任务,ForkJoinPool
负责管理这些任务的执行。
假设有一个计算数组元素总和的 SumTask
(继承自 RecursiveTask
),可以使用 invoke()
方法来执行这个任务并获取结果:
// 创建一个包含随机数的数组
long[] numbers = new long[1000];
// 填充数组...
// 创建一个ForkJoinTask来计算数组的总和
ForkJoinTask<Long> task = new SumTask(numbers, 0, numbers.length);
// 创建ForkJoinPool并使用invoke方法执行任务
Long result = new ForkJoinPool().invoke(task);
// 输出结果
System.out.println("Total sum: " + result);
在这个示例中,new ForkJoinPool().invoke(task)
会在 ForkJoinPool
中执行 SumTask
任务,并等待任务完成。invoke()
方法返回 SumTask
的计算结果,即数组元素的总和。
invoke()
方法会导致调用线程阻塞,直到任务完成。这意味着如果任务执行时间很长,调用线程也会被长时间阻塞。ForkJoinPool
采用工作窃取算法来优化任务的执行,但是如果任务分解得不合理(太细或太粗),可能会影响性能。ForkJoinPool
实例,因为每个 ForkJoinPool
实例都会创建许多线程,过多的线程可能会导致系统资源紧张,降低性能。在大多数情况下,使用 ForkJoinPool
的静态共享实例(通过 ForkJoinPool.commonPool()
获取)是足够的。