跳转到内容

Fork/Join - ForkJoinTask

草稿难度:中级#JUC#SQL

原文:CSDN(历史文章导入,当前状态为草稿)

Fork/Join框架核心组件:ForkJoinTask详解

Section titled “Fork/Join框架核心组件:ForkJoinTask详解”

Fork/Join框架是Java 7引入的一个强大的并行处理框架,专为可以递归分解的问题设计。在这个框架中,ForkJoinTask是核心组件,它提供了支持分治算法的任务抽象。

ForkJoinTask本质上是Java 并发编程 中的一种轻量级任务抽象,是Fork/Join框架的核心组件。它是一个抽象类,代表在ForkJoinPool中执行的任务单元。与常规任务不同,ForkJoinTask特别适用于可以分解为更小子任务的工作。

public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
// 任务的状态信息
volatile int status; // 任务状态
// 任务的结果(根据泛型类型V而定)
// ...
}

ForkJoinTask与传统的Thread有本质区别,了解这些区别有助于我们正确选择并发工具:

  1. 轻量级:ForkJoinTask比Thread更轻量,可创建数百万个实例而不会耗尽系统资源
  2. 实现机制:Thread直接映射到操作系统线程,而ForkJoinTask是由ForkJoinPool管理的任务对象
  3. 执行模型:Thread是执行者,而ForkJoinTask是被执行的任务,需要ForkJoinPool中的工作线程来执行
  4. 分治支持:ForkJoinTask专为分治算法设计,支持任务的分解(fork)和结果合并(join)
  5. 调度方式:Thread由操作系统调度,ForkJoinTask由ForkJoinPool基于工作窃取算法调度

理解 类 比:Thread就像是厨师(执行者),而ForkJoinTask则像是菜谱(任务描述)。更准确地说,ForkJoinTask是一种特殊的“分步骤菜谱”,它能够说“如果这道菜太复杂,可以先把它分解成几个小菜,各自完成后再组合起来”。

在处理大规模数据并行计算时,可能需要创建成千上万个子任务。如果每个子任务都对应一个Thread:

  1. 系统资源很快耗尽(线程创建开销大)
  2. 上下文切换成本高昂
  3. 手动管理任务间依赖关系复杂

而ForkJoinTask作为普通Java对象,创建成本低,且框架自动处理任务调度和结果 合并 ,让开发者专注于业务逻辑而非并发控制。

public final ForkJoinTask<V> fork() {
// 获取当前线程
Thread t;
// 如果当前线程是ForkJoinWorkerThread类型,则将任务推入工作队列
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
// 否则,由common pool处理
ForkJoinPool.common.externalPush(this);
return this;
}

作用:异步执行此任务,通常将任务添加到当前执行线程的工作队列。此方法立即返回,不等待任务执行完成。

关键点

  • fork()是非阻塞的,调用后立即返回
  • 它不会创建新线程,只是将任务放入队列
  • 通常任务会被放入当前工作线程的队列头部
  • 初学者常误解fork()为立即创建新线程执行,实际不是
public final V join() {
int s;
// 通过doJoin()执行实际的join操作,获取任务状态
if ((s = doJoin() & DONE_MASK) != NORMAL)
// 如果有异常,报告异常
reportException(s);
// 返回任务结果
return getRawResult();
}
// doJoin方法的核心实现
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
// 如果任务已完成,返回状态
return (s = status) < 0 ? s :
// 如果当前线程是工作线程,执行tryUnpush+doExec进行协助执行
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
// 否则通过外部调用者执行
externalAwaitDone();
}

作用:等待任务完成并返回结果。如果任务已完成,立即返回结果;如果任务未完成,当前线程会等待直到任务执行完毕。

关键点

  • join()是阻塞操作,但不同于Thread.join()
  • 在等待过程中,工作线程不会闲置,而是会:
    • 尝试执行队列中其他任务
    • 尝试帮助完成当前等待的任务
    • 尝试窃取其他队列的任务
  • 这种“主动等待”是ForkJoinTask性能优势的关键
public final V invoke() {
int s;
// 执行任务并得到状态
if ((s = doInvoke() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private int doInvoke() {
int s; Thread t; ForkJoinWorkerThread wt;
// 尝试执行任务
if ((s = doExec()) < 0)
return s;
// 如果没完成,根据当前线程类型决定如何等待完成
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
return (wt = (ForkJoinWorkerThread)t).pool.
awaitJoin(wt.workQueue, this, 0L);
else
return externalAwaitDone();
}

作用:开始执行此任务,等待完成,然后返回结果。相当于同步执行当前任务并等待其完成。

区别

  • invoke()会直接执行任务,而fork()只是将任务提交到队列
  • invoke()是阻塞的,类似于直接调用compute()并等待结果
  • 通常用于顶层任务的执行
// 这是一个抽象方法,由子类实现
protected abstract V compute();

作用:定义任务的实际执行逻辑。由子类实现,通常包含任务的分解、执行和结果合并的代码。

典型实现模式

protected Result compute() {
if (任务足够小) {
// 直接计算
return 直接计算结果();
} else {
// 任务分解
ChildTask1 task1 = new ChildTask1(...);
ChildTask2 task2 = new ChildTask2(...);
// 异步执行一个子任务
task1.fork();
// 同步执行另一个子任务
Result result2 = task2.compute();
// 等待异步子任务并获取结果
Result result1 = task1.join();
// 合并结果
return 合并(result1, result2);
}
}
  1. invokeAll()
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
t1.fork();
t2.invoke();
t1.join();
}

作用:并行执行多个任务,等待所有任务完成。是fork/compute/join模式的便捷包装。

  1. tryUnfork():尝试取消已提交但未开始执行的任务。
  2. isDone()/isCompletedNormally()/isCompletedAbnormally():检查任务状态。
  3. completeExceptionally():使任务以异常方式完成。
  4. quietlyJoin()/quietlyInvoke():等待任务但不抛出异常。

ForkJoinTask有三个重要的子类,针对不同场景设计:

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
/**
* 唯一抽象方法,实现具体计算逻辑
*/
protected abstract V compute();
/**
* 返回计算结果
*/
public final V getRawResult() { return result; }
/**
* 设置结果
*/
protected final void setRawResult(V value) {
result = value;
}
/**
* 执行计算
*/
protected final boolean exec() {
result = compute();
return true;
}
private V result;
}

特点

  • 有明确的返回值类型V
  • 需要重写compute()方法,该方法返回类型V的结果
  • 适用于需要返回计算结果的场景

经典用例

  • 并行计算数组总和
  • 并行搜索(如查找最大值)
  • 并行排序
  • 并行统计计算

4.2 RecursiveAction - 无返回值的任务

Section titled “4.2 RecursiveAction - 无返回值的任务”
public abstract class RecursiveAction extends ForkJoinTask<Void> {
/**
* 实现具体执行逻辑,无返回值
*/
protected abstract void compute();
/**
* 永远返回null
*/
public final Void getRawResult() { return null; }
/**
* 无效方法,因为没有结果
*/
protected final void setRawResult(Void value) { }
/**
* 执行compute方法
*/
protected final boolean exec() {
compute();
return true;
}
}

特点

  • 无返回值(void)
  • 需要重写compute()方法,该方法无返回值
  • 适用于“只做不返回”的并行操作

经典用例

  • 并行数组元素转换
  • 并行文件处理
  • 并行图像处理
  • 任何不需要返回结果的批量操作

4.3 CountedCompleter - 带完成回调的任务

Section titled “4.3 CountedCompleter - 带完成回调的任务”
public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
// 父任务引用
final CountedCompleter<?> completer;
// 待完成的子任务计数
volatile int pending;
// 核心方法:实际需要执行的计算
public abstract void compute();
// 当所有子任务和当前任务完成时调用
public void onCompletion(CountedCompleter<?> caller) { }
// 减少挂起计数
public final void tryComplete() {
CountedCompleter<?> a = this, s = a;
for (int p;;) {
// 检查是否还有挂起的子任务
if ((p = a.pending) == 0) {
// 执行完成回调
a.onCompletion(s);
// 尝试完成父任务
if ((a = (s = a).completer) == null)
break;
// ...
}
// ...
}
// ...
}
// ...其他方法
}

特点

  • 支持复杂的完成触发机制
  • 适合任务间有依赖关系的场景
  • 可以在任务完成时触发回调操作
  • 支持更灵活的任务控制流

经典用例

  • 有复杂依赖关系的任务执行
  • 需要在特定阶段通知或触发其他操作的并行任务
  • 构建复杂的异步执行树
  • 搜索任务,找到结果后通知其他任务取消

ForkJoinTask的工作原理基于分治算法(Divide and Conquer),其核心过程包括:

  1. 任务判断:评估当前任务规模是否小于某个阈值
  2. 任务分割:如果任务较大,将其分解为更小的子任务
  3. 任务执行:并行执行子任务(部分异步、部分同步)
  4. 结果合并:将子任务结果组合成最终结果

这个过程用图形表示如下:

大任务
/ \
子任务1 子任务2
/ \ / \
小任务1 小任务2 小任务3 小任务4

在实际实现中,有一个非常重要但容易被忽视的优化模式:

protected Result compute() {
if (任务足够小) {
return 直接计算();
}
// 分割任务
LeftTask left = new LeftTask(...);
RightTask right = new RightTask(...);
// 只异步执行left
left.fork();
// 直接在当前线程执行right
Result rightResult = right.compute();
// 等待left完成并获取结果
Result leftResult = left.join();
// 合并结果
return merge(leftResult, rightResult);
}

为什么这样做?

  1. 减少任务创建开销:如果所有子任务都fork(),会增加任务调度开销
  2. 提高局部性:直接执行一个子任务可以利用CPU缓存,提高性能
  3. 减少工作窃取:工作窃取虽然有效,但仍有开销,适度减少可提高效率

实际上,不推荐的实现方式是:

// 不推荐的方式
left.fork();
right.fork(); // 没必要fork两个任务
leftResult = left.join();
rightResult = right.join();

任务分割的粒度(阈值)是影响性能的关键因素:

  • 粒度过细:任务太小,调度开销超过并行收益
  • 粒度过粗:任务太大,无法充分利用并行性

经验法则:

  1. 基本计算量:100-10000个基本操作为宜
  2. 动态调整:根据实际硬件和问题特性调整
  3. 基准测试:通过测试找到最佳阈值

示例代码:

private static final int THRESHOLD = 1000; // 根据基准测试确定
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 任务足够小,直接计算
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
// 否则分割任务
int mid = (start + end) >>> 1;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork();
long rightResult = rightTask.compute();
long leftResult = leftTask.join();
return leftResult + rightResult;
}

ForkJoinPool是ForkJoinTask的执行环境,其核心组件包括:

  1. 工作线程(ForkJoinWorkerThread)

    • 执行队列中任务的线程
    • 数量通常等于CPU核心数
  2. 工作队列(WorkQueue)

    • 每个工作线程一个,是双端队列(deque)
    • 支持线程从队列前端(LIFO)获取任务
    • 支持其他线程从队列后端(FIFO)窃取任务
  3. 提交队列

    • 用于外部提交的任务

6.2 ForkJoinTask与ForkJoinPool的协作流程

Section titled “6.2 ForkJoinTask与ForkJoinPool的协作流程”
┌─────────────────────────────────────┐
│ ForkJoinPool │
│ │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Worker1│ │Worker2│ .. │WorkerN│ │
│ └───┬───┘ └───┬───┘ └───┬───┘ │
│ │ │ │ │
│ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ │
│ │Queue1 │ │Queue2 │ .. │QueueN │ │
│ └───────┘ └───────┘ └───────┘ │
└─────────────────────────────────────┘

协作流程:

  1. 任务提交

    • 外部提交:任务被分配到某个工作队列
    • 内部fork():任务被放入当前线程的工作队列头部
  2. 任务执行

    • 工作线程优先从自己队列的头部获取任务(LIFO)
    • 自己队列为空时,尝试从其他队列尾部窃取任务(FIFO)
  3. 任务等待

    • 调用join()时,如果任务未完成,线程会尝试:
      • 执行该任务(如果可能)
      • 执行其他任务
      • 帮助完成依赖的任务

工作窃取(Work-Stealing)是ForkJoinPool的核心调度算法,工作原理如下:

// ForkJoinPool内部的任务窃取逻辑伪代码
private ForkJoinTask<?> scan(boolean isPollingMode) {
WorkQueue[] queues = this.queues;
int n = queues.length;
// 随机起点,减少冲突
int r = ThreadLocalRandom.current().nextInt(n);
for (int i = 0; i < n; i++) {
int j = (r + i) % n;
WorkQueue q = queues[j];
if (q != null) {
// 尝试从尾部窃取任务(FIFO)
ForkJoinTask<?> task = q.pollLast();
if (task != null)
return task;
}
}
return null; // 未找到可窃取的任务
}

关键特性

  1. 双端队列(Deque)

    • 拥有者线程从前端操作(LIFO):有利于保持缓存一致性
    • 窃取者从后端操作(FIFO):减少冲突,窃取最早入队的大任务
  2. 随机窃取目标

    • 随机选择“受害者”队列,分散冲突
    • 窃取失败时轮询或退避
  3. 窃取规则

    • 只窃取大型任务(最外层任务)
    • 局部性优先,只有自己无任务时才窃取

工作窃取的优势

  1. 自动负载均衡:忙线程的任务会被闲线程分担
  2. 减少竞争:大部分情况下线程操作自己的队列,无争用
  3. 提高缓存命中率:优先处理最近生成的相关任务
  4. 几乎无阻塞:无中央调度点,减少同步开销

ForkJoinTask的join()方法是其最独特的方面之一,它实现了“工作窃取等待”(Work-Stealing Waiting):

private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
// 任务已完成,直接返回状态
return (s = status) < 0 ? s :
// 如果当前线程是工作线程
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
// 尝试从队列取消推送该任务并执行
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
// 或者等待任务完成,这个过程中会执行其他任务!
wt.pool.awaitJoin(w, this, 0L) :
// 外部调用者
externalAwaitDone();
}

awaitJoin核心逻辑

// ForkJoinPool中的awaitJoin方法(简化版)
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
int s = 0;
if (task != null && w != null) {
ForkJoinTask<?> prevJoin = w.currentJoin;
w.currentJoin = task;
// 记录入队检查次数
int noCycles = 0;
do {
// 检查任务是否完成
if ((s = task.status) < 0)
break;
// 如果太多次检查未成功,帮助完成任务
if (++noCycles > 2) {
helpStealer(w, task); // 尝试帮助偷取任务的线程
// 或者执行其他任务
pollAndExecAll();
// 随机休眠避免忙等
Thread.yield();
}
} while (s >= 0);
w.currentJoin = prevJoin;
}
return s;
}

主动等待的关键点

  • 等待时不是闲置,而是执行其他任务
  • 尝试帮助完成被等待的任务或其依赖
  • 通过随机退避避免活锁

经典的ForkJoinTask应用,将数组划分为小块并行计算总和:

public class ArraySumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start;
private final int end;
private static final int THRESHOLD = 1000; // 阈值
public ArraySumTask(long[] array, int start, int end) {
this.array = array;
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 += array[i];
}
return sum;
}
// 任务分割
int middle = (start + end) >>> 1;
ArraySumTask leftTask = new ArraySumTask(array, start, middle);
ArraySumTask rightTask = new ArraySumTask(array, middle, end);
// 并行执行
leftTask.fork();
long rightResult = rightTask.compute(); // 直接执行右侧任务
long leftResult = leftTask.join(); // 等待左侧任务
// 合并结果
return leftResult + rightResult;
}
public static long sumArray(long[] array) {
return new ForkJoinPool().invoke(new ArraySumTask(array, 0, array.length));
}
}

使用示例

long[] array = new long[100_000_000];
// 初始化数组...
long sum = ArraySumTask.sumArray(array);
System.out.println("Sum: " + sum);

使用ForkJoinTask实现并行快速排序:

public class ParallelQuickSort extends RecursiveAction {
private final int[] array;
private final int low;
private final int high;
private static final int THRESHOLD = 1000;
public ParallelQuickSort(int[] array, int low, int high) {
this.array = array;
this.low = low;
this.high = high;
}
@Override
protected void compute() {
if (high - low <= THRESHOLD) {
// 小数组直接使用标准排序
Arrays.sort(array, low, high + 1);
return;
}
// 快速排序分区
int pivot = partition(array, low, high);
// 并行处理两个分区
ParallelQuickSort left = new ParallelQuickSort(array, low, pivot - 1);
ParallelQuickSort right = new ParallelQuickSort(array, pivot + 1, high);
// 使用invokeAll并行执行两个子任务
invokeAll(left, right);
}
private int partition(int[] array, int low, int high) {
// 标准快速排序分区算法
int pivot = array[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (array[j] <= pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, high);
return i + 1;
}
private void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void sort(int[] array) {
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new ParallelQuickSort(array, 0, array.length - 1));
}
}

使用RecursiveAction并行处理多个文件:

public class FileProcessorTask extends RecursiveAction {
private final List<File> files;
private final int start;
private final int end;
private final FileProcessor processor;
private static final int THRESHOLD = 10; // 每个任务处理的最大文件数
public FileProcessorTask(List<File> files, int start, int end, FileProcessor processor) {
this.files = files;
this.start = start;
this.end = end;
this.processor = processor;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
// 处理范围内的所有文件
for (int i = start; i < end; i++) {
try {
processor.processFile(files.get(i));
} catch (Exception e) {
// 错误处理
e.printStackTrace();
}
}
return;
}
// 文件数量较多,分割任务
int middle = (start + end) >>> 1;
FileProcessorTask leftTask = new FileProcessorTask(files, start, middle, processor);
FileProcessorTask rightTask = new FileProcessorTask(files, middle, end, processor);
// 并行执行
invokeAll(leftTask, rightTask);
}
// 文件处理器接口
public interface FileProcessor {
void processFile(File file) throws Exception;
}
// 使用示例
public static void processFiles(List<File> files, FileProcessor processor) {
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new FileProcessorTask(files, 0, files.size(), processor));
}
}

并行搜索算法,找到匹配项后取消其他任务:

public class ParallelSearcher<T> extends CountedCompleter<T> {
private final T[] array;
private final int start;
private final int end;
private final Predicate<T> condition;
private final AtomicReference<T> result;
private static final int THRESHOLD = 1000;
public ParallelSearcher(CountedCompleter<?> parent, T[] array, int start, int end,
Predicate<T> condition, AtomicReference<T> result) {
super(parent);
this.array = array;
this.start = start;
this.end = end;
this.condition = condition;
this.result = result;
}
@Override
public void compute() {
// 如果已找到结果或任务足够小,直接处理
if (result.get() != null || end - start <= THRESHOLD) {
for (int i = start; i < end && result.get() == null; i++) {
T element = array[i];
if (condition.test(element)) {
result.compareAndSet(null, element);
// 已找到结果,尝试完成所有任务
quietlyCompleteRoot();
break;
}
}
tryComplete();
return;
}
// 分割任务
int mid = (start + end) >>> 1;
// 分别负责处理前半部分和后半部分
addToPendingCount(2); // 增加子任务计数
// 创建并执行子任务
new ParallelSearcher<>(this, array, start, mid, condition, result).fork();
new ParallelSearcher<>(this, array, mid, end, condition, result).fork();
}
private void quietlyCompleteRoot() {
for (CountedCompleter<?> c = this; c != null; c = c.getCompleter()) {
c.quietlyComplete();
}
}
// 使用示例
public static <T> Optional<T> search(T[] array, Predicate<T> condition) {
AtomicReference<T> result = new AtomicReference<>();
ForkJoinPool pool = new ForkJoinPool();
ParallelSearcher<T> searcher =
new ParallelSearcher<>(null, array, 0, array.length, condition, result);
pool.invoke(searcher);
return Optional.ofNullable(result.get());
}
}

8. ForkJoinTask的高级特性与使用技巧

Section titled “8. ForkJoinTask的高级特性与使用技巧”

8.1 处理IO操作 - ManagedBlocker的应用

Section titled “8.1 处理IO操作 - ManagedBlocker的应用”

ForkJoinTask主要用于CPU密集型计算,但有时需要在任务中执行IO操作。直接执行IO会阻塞工作线程,降低并行度。应对方法是使用ManagedBlocker:

public class FileReaderBlocker implements ForkJoinPool.ManagedBlocker {
private final String filePath;
private String content;
private boolean done = false;
public FileReaderBlocker(String filePath) {
this.filePath = filePath;
}
@Override
public boolean block() throws InterruptedException {
if (!done) {
try {
// 执行IO操作
content = Files.readString(Path.of(filePath));
done = true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return true;
}
@Override
public boolean isReleasable() {
return done;
}
public String getContent() {
# Fork/Join框架核心组件:ForkJoinTask详解(续)
## 8. ForkJoinTask的高级特性与使用技巧(续)
### 8.1 处理IO操作 - ManagedBlocker的应用
public class FileReaderBlocker implements ForkJoinPool.ManagedBlocker {
private final String filePath;
private String content;
private boolean done = false;
public FileReaderBlocker(String filePath) {
this.filePath = filePath;
}
@Override
public boolean block() throws InterruptedException {
if (!done) { // 如果尚未完成IO操作
try {
// 执行IO操作(阻塞)
content = Files.readString(Path.of(filePath));
done = true; // 标记操作完成
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return true; // 表示操作已完成
}
@Override
public boolean isReleasable() {
return done; // 如果已完成则返回true,表示可以释放线程
}
public String getContent() {
return content;
}
}

使用方式:

public class IOAwareTask extends RecursiveTask<String> {
private final String filePath;
public IOAwareTask(String filePath) {
this.filePath = filePath;
}
@Override
protected String compute() {
// 创建ManagedBlocker实现
FileReaderBlocker blocker = new FileReaderBlocker(filePath);
try {
// 通知ForkJoinPool即将阻塞
ForkJoinPool.managedBlock(blocker);
// 获取IO操作的结果
return blocker.getContent();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}

ManagedBlocker工作原理:

  1. 感知阻塞:当使用ForkJoinPool.managedBlock()时,ForkJoinPool会意识到即将发生阻塞
  2. 检查可释放状态:首先调用isReleasable()检查是否可以跳过阻塞
  3. 创建补偿线程:如果需要阻塞,ForkJoinPool可能会创建额外的工作线程来维持并行度
  4. 执行阻塞操作:调用block()方法执行实际的阻塞操作
  5. 恢复执行:阻塞操作完成后,继续执行后续代码

通过这种方式,ForkJoinPool可以在工作线程阻塞时动态调整线程数量,保持整体并行处理能力。

ForkJoinTask处理异常的方式与普通Java代码有所不同,它不会立即抛出异常,而是将异常保存起来,直到调用某些方法时才抛出:

public class ExceptionHandlingTask extends RecursiveTask<Integer> {
@Override
protected Integer compute() {
try {
// 可能抛出异常的代码
if (someCondition) {
throw new RuntimeException("Task failed");
}
return result;
} catch (Exception e) {
// 显式调用处理异常的方法
return handleException(e);
}
}
private Integer handleException(Exception e) {
// 选择1:重新包装并抛出
throw new CompletionException(e);
// 选择2:返回默认值
// return defaultValue;
// 选择3:使用completeExceptionally
// completeExceptionally(e);
// return null;
}
}

异常传播规则:

  1. fork()后的异常:通过fork()提交的任务中的异常不会立即抛出,而是在调用join()或get()时抛出
  2. invoke()中的异常:通过invoke()执行的任务中的异常会立即传播
  3. 检查异常状态:可以使用isCompletedAbnormally()检查任务是否异常完成
  4. 获取异常:使用getException()获取保存的异常

最佳实践:

try {
Result result = task.invoke(); // 或 task.join()
// 处理正常结果
} catch (CompletionException | RuntimeException e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
// 处理具体异常
if (cause instanceof SpecificException) {
// 处理特定类型异常
} else {
// 处理其他异常
}
}

ForkJoinTask支持任务取消,但取消机制需要任务主动配合:

public class CancellableTask extends RecursiveAction {
@Override
protected void compute() {
// 在任务执行过程中定期检查任务是否被取消
for (int i = 0; i < steps && !isCancelled(); i++) {
// 执行部分工作
doPartialWork(i);
// 检查线程中断状态
if (Thread.currentThread().isInterrupted()) {
cancel(false); // 标记任务为已取消
return;
}
}
}
private void doPartialWork(int step) {
// 实际工作...
}
}

取消相关方法:

// 标记任务为已取消状态
boolean cancel(boolean mayInterruptIfRunning);
// 检查任务是否被取消
boolean isCancelled();
// 取消所有子任务
public static void cancelChildren(ForkJoinTask<?> task) {
if (task instanceof CountedCompleter) {
ForkJoinTask<?> child = task.firstChild;
while (child != null) {
child.cancel(false);
child = child.nextSibling;
}
}
}

取消传播:

public class PropagatingCancellationTask<T> extends RecursiveTask<T> {
private List<ForkJoinTask<T>> subTasks = new ArrayList<>();
@Override
protected T compute() {
if (简单任务) {
return 直接计算();
}
// 创建子任务
for (int i = 0; i < parts; i++) {
PropagatingCancellationTask<T> subTask = new PropagatingCancellationTask<>(/* 参数 */);
subTasks.add(subTask);
subTask.fork();
}
T result = null;
// 等待结果并检查取消状态
for (ForkJoinTask<T> task : subTasks) {
try {
T subResult = task.join();
// 合并结果
result = combine(result, subResult);
} catch (CancellationException ce) {
// 一个子任务被取消,则取消所有其他子任务
for (ForkJoinTask<T> otherTask : subTasks) {
otherTask.cancel(true);
}
throw ce; // 继续传播取消
}
}
return result;
}
}

ForkJoinTask本身不支持直接的优先级控制,但可以通过一些技巧影响执行顺序:

方法1:调整任务的提交顺序

// 重要任务先fork
importantTask.fork();
// 次要任务后fork
lessImportantTask.fork();

方法2:直接执行vs异步执行

// 高优先级任务直接执行
highPriorityResult = highPriorityTask.compute();
// 低优先级任务fork后再join
lowPriorityTask.fork();
lowPriorityResult = lowPriorityTask.join();

方法3:使用不同的ForkJoinPool

// 为不同优先级的任务使用不同的线程池
ForkJoinPool highPriorityPool = new ForkJoinPool(
Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
ForkJoinPool lowPriorityPool = new ForkJoinPool(
Runtime.getRuntime().availableProcessors() / 2, // 使用更少的线程
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, false);
// 提交到相应的池
Future<Result> highPriorityFuture = highPriorityPool.submit(highPriorityTask);
Future<Result> lowPriorityFuture = lowPriorityPool.submit(lowPriorityTask);

Java 8引入的并行流在内部使用ForkJoinPool和ForkJoinTask进行实现。理解这种关联有助于更好地使用并行流:

// 并行流的使用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.mapToInt(n -> n * n)
.sum();

内部实现原理:

  1. 分割数据源:调用parallelStream()时,集合会创建一个Spliterator负责数据分割
  2. 创建任务:流操作被转换为ForkJoinTask任务
  3. 任务执行:任务提交到ForkJoinPool.commonPool()执行
  4. 合并结果:各个任务的结果使用适当的方式合并(如sum、collect等)
Stream API Fork/Join Framework
┌───────────────┐ ┌───────────────────┐
│ Stream │ │ ForkJoinPool │
│ ├─Sequential │ │ │
│ └─Parallel │─────────▶│ ├─commonPool() │
│ │ │ │
│ Spliterator │─────────▶│ ForkJoinTask │
└───────────────┘ └───────────────────┘

关键实现类:

  • AbstractTask:Stream操作的ForkJoinTask实现
  • ForEachOp:forEach操作的并行实现
  • ReduceOp:reduce操作的并行实现

虽然底层使用相同的机制,但两者存在显著差异:

特性 并行流 直接使用ForkJoinTask
编程模型 声明式,函数式 命令式,面向对象
使用难度 简单,只需添加parallel()调用 较复杂,需实现分治逻辑
控制粒度 有限,主要通过Spliterator控制 完全掌控,可自定义任何分割策略
异常处理 自动传播异常 需要显式处理或在join()时捕获
适用场景 简单的集合并行操作 复杂的自定义并行算法

选择建议:

  • 使用并行流当

    • 操作简单的集合数据
    • 需要快速实现并行
    • 处理标准的过滤、映射、归约操作
  • 使用ForkJoinTask当

    • 需要精细控制任务分割
    • 有复杂的任务依赖关系
    • 需要自定义结果合并逻辑
    • 对性能有极高要求

并行流实现数组求和:

public static long sumWithParallelStream(long[] array) {
return Arrays.stream(array)
.parallel()
.sum();
}

ForkJoinTask实现相同功能:

public static long sumWithForkJoin(long[] array) {
ForkJoinPool pool = ForkJoinPool.commonPool();
return pool.invoke(new SumTask(array, 0, array.length));
}
static class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start;
private final int end;
private static final int THRESHOLD = 1000;
SumTask(long[] array, int start, int end) {
this.array = array;
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 += array[i];
}
return sum;
}
int mid = (start + end) >>> 1;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
long rightResult = right.compute();
long leftResult = left.join();
return leftResult + rightResult;
}
}

性能比较:

  • 并行流实现更简洁,但控制粒度有限
  • ForkJoinTask实现更详细,但可以精确控制分割阈值和执行策略
  • 对于简单操作,两者性能相近,复杂场景下直接使用ForkJoinTask通常更高效

10. ForkJoinTask与CompletableFuture和传统线程池的比较

Section titled “10. ForkJoinTask与CompletableFuture和传统线程池的比较”
特性 ForkJoinTask CompletableFuture 传统线程池
核心设计目标 支持分治并行算法 支持异步编程和任务组合 通用任务执行
任务关系模型 父子层次结构 依赖关系图 独立任务
线程模型 工作窃取队列 基于线程池的执行器 工作队列+固定线程
任务粒度 可递归分解的小任务 单个离散任务 独立且相对较大的任务
等待行为 主动等待 回调模式 被动阻塞
API风格 面向对象,继承式 函数式,链式调用 命令模式

10.2 源码解析:三种模型的工作方式

Section titled “10.2 源码解析:三种模型的工作方式”

ForkJoinTask工作方式:

// 在ForkJoinTask内部,任务会被递归分解
@Override
protected Integer compute() {
if (workload小于阈值) {
return 直接计算结果();
}
// 分解任务
Task1 left = new Task1(分解参数);
Task2 right = new Task2(分解参数);
left.fork();
Integer rightResult = right.compute();
Integer leftResult = left.join();
// 合并结果
return leftResult + rightResult;
}

CompletableFuture工作方式:

// CompletableFuture内部使用回调链处理异步任务
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {
return uniApplyStage(null, fn);
}
private <V> CompletableFuture<V> uniApplyStage(Executor e, Function<? super T, ? extends V> f) {
// 创建新的CompletableFuture表示转换后的结果
CompletableFuture<V> dst = new CompletableFuture<V>();
// 创建一个依赖当前任务完成的回调
UniApply<T, V> c = new UniApply<T, V>(e, dst, this, f);
// 将回调压入完成栈
push(c);
// 检查当前任务是否已完成,如果是,则立即执行回调
if (isCompletedPredicate())
c.tryFire(SYNC);
return dst;
}

传统线程池工作方式:

// ThreadPoolExecutor处理提交的Runnable/Callable任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 尝试各种策略执行任务:
// 1. 如果运行的线程少于corePoolSize,则创建新线程执行任务
// 2. 如果任务成功进入队列,还需要再次检查是否应该添加线程
// 3. 如果无法将任务加入队列,则创建新线程,如果失败则拒绝任务
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

ForkJoinTask适用场景:

  • 递归分解问题:如归并排序、快速排序
  • 大数据集并行处理:数据分析、矩阵计算
  • CPU密集型任务:图像处理、数值计算
  • 需要合并子结果的场景:MapReduce类型操作
// ForkJoinTask示例 - 图像处理
public class ImageProcessingTask extends RecursiveAction {
private final int[] pixels;
private final int start, end;
private final Function<Integer, Integer> filter;
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
// 处理这一段像素
for (int i = start; i < end; i++) {
pixels[i] = filter.apply(pixels[i]);
}
} else {
int middle = (start + end) >>> 1;
invokeAll(
new ImageProcessingTask(pixels, start, middle, filter),
new ImageProcessingTask(pixels, middle, end, filter)
);
}
}
}

CompletableFuture适用场景:

  • 异步IO操作:网络请求、数据库操作
  • 事件驱动程序:响应服务、消息处理
  • 任务编排:多步骤流程、依赖任务链
  • 混合计算/IO场景:需同时处理计算和等待
// CompletableFuture示例 - 多服务调用编排
public CompletableFuture<OrderResult> processOrder(Order order) {
return CompletableFuture.supplyAsync(() -> userService.getUser(order.getUserId()))
.thenCombine(
CompletableFuture.supplyAsync(() -> inventoryService.checkAvailability(order.getItems())),
(user, inventory) -> new ValidatedOrder(user, order, inventory)
)
.thenCompose(validOrder ->
CompletableFuture.supplyAsync(() -> paymentService.processPayment(validOrder))
)
.thenApply(payment ->
orderService.finalizeOrder(payment)
)
.exceptionally(ex -> {
logError(ex);
return createFailedOrderResult(ex, order);
});
}

传统线程池适用场景:

  • 独立任务执行:离散的工作单元
  • 任务量可预测:线程数容易确定的场景
  • 长时间运行任务:后台作业、监控任务
  • 需要精细控制资源:严格限制线程数量
// 传统线程池示例 - 处理各种请求
ExecutorService executor = new ThreadPoolExecutor(
10, 20, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500),
new ThreadPoolExecutor.CallerRunsPolicy());
// 提交各种任务到线程池
Future<Response> future1 = executor.submit(() -> processRequest(request1));
Future<Response> future2 = executor.submit(() -> processRequest(request2));

选择指南:

根据以下因素选择合适的并发工具:

  1. 任务特性

    • 可分解为小任务?→ ForkJoinTask
    • 有依赖关系链?→ CompletableFuture
    • 独立离散任务?→ 传统线程池
  2. 操作类型

    • CPU密集型?→ ForkJoinTask
    • IO密集型?→ CompletableFuture或传统线程池
    • 混合型?→ CompletableFuture
  3. 并行模型

    • 数据并行?→ ForkJoinTask
    • 任务并行?→ 传统线程池
    • 异步流程?→ CompletableFuture
  4. 资源控制

    • 自动适应?→ ForkJoinTask
    • 严格限制?→ 传统线程池
    • 弹性控制?→ 使用定制的CompletableFuture执行器

11. ForkJoinTask的最佳实践与常见问题

Section titled “11. ForkJoinTask的最佳实践与常见问题”

1. 合理设置阈值

// 不同类型任务的合理阈值范围
private static int determineThreshold(TaskType type, int dataSize) {
switch (type) {
case ARITHMETIC: return Math.max(1000, dataSize / (Runtime.getRuntime().availableProcessors() * 2));
case IO_INTENSIVE: return Math.max(50, dataSize / (Runtime.getRuntime().availableProcessors() * 8));
case MIXED: return Math.max(500, dataSize / (Runtime.getRuntime().availableProcessors() * 4));
default: return 1000;
}
}

2. 避免过度细粒度任务

// 避免过多的小任务创建
@Override
protected Long compute() {
if (end - start <= 10) { // 太小的任务直接计算
return computeDirectly();
}
if (end - start <= THRESHOLD) {
// 适当大小的中等任务,自己计算
return computeSequentially();
}
// 足够大的任务,分解并行处理
int mid = (start + end) >>> 1;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
long rightResult = right.compute();
long leftResult = left.join();
return leftResult + rightResult;
}

3. 使用正确的fork-compute-join模式

// 推荐模式 - 左侧fork,右侧直接compute
leftTask.fork();
rightResult = rightTask.compute(); // 直接执行右侧
leftResult = leftTask.join();
return merge(leftResult, rightResult);
// 不推荐模式 - 两侧都fork
leftTask.fork();
rightTask.fork(); // 不必要的fork
leftResult = leftTask.join();
rightResult = rightTask.join();

4. 使用invokeAll简化多任务执行

// 处理多个子任务
invokeAll(task1, task2, task3, task4);
// 等价于但更简洁
task1.fork();
task2.fork();
task3.fork();
task4.compute();
task3.join();
task2.join();
task1.join();

5. 避免任务中的资源竞争

// 不好的方式 - 所有任务共享并竞争同一资源
class BadTask extends RecursiveAction {
private static final AtomicInteger sharedCounter = new AtomicInteger();
@Override
protected void compute() {
// 所有任务都更新同一计数器,造成竞争
sharedCounter.incrementAndGet(); // 热点竞争!
// ...
}
}
// 更好的方式 - 局部计算然后合并
class BetterTask extends RecursiveTask<Integer> {
@Override
protected Integer compute() {
if (small) {
return localComputation(); // 局部计算,无共享状态
}
BetterTask left = new BetterTask(...);
BetterTask right = new BetterTask(...);
left.fork();
int rightCount = right.compute();
int leftCount = left.join();
return leftCount + rightCount; // 合并结果
}
}

1. 问题:任务太小,导致调度开销超过并行收益

// 问题代码 - 阈值过小
if (end - start > 1) { // 阈值只有1!
int mid = (start + end) >>> 1;
Task left = new Task(array, start, mid);
Task right = new Task(array, mid, end);
left.fork();
right.fork();
left.join();
right.join();
}

解决方案:

  • 增加阈值,减少任务数量
  • 使用基准测试确定最佳阈值
  • 考虑数据规模和处理器数量动态调整阈值

2. 问题:使用错误的fork-join模式

// 问题代码 - 不必要的fork
left.fork();
right.fork(); // 应该直接compute
result1 = left.join();
result2 = right.join();

解决方案:

  • 遵循“一个fork一个compute”原则
  • 对其中一个子任务直接调用compute()
  • 使用invokeAll来简化多任务调用

3. 问题:忽略异常处理

// 问题代码 - 未处理异常
public void processTasks(List<MyTask> tasks) {
for (MyTask task : tasks) {
task.fork();
}
for (MyTask task : tasks) {
task.join(); // 异常被吞噬或延迟
}
}

解决方案:

  • 在join()调用周围使用try-catch
  • 使用ForkJoinTask的isCompletedAbnormally()和getException()检查异常
  • 考虑使用quietlyJoin()和自定义异常处理

4. 问题:任务中的阻塞操作

// 问题代码 - 在任务中直接阻塞
@Override
protected Result compute() {
try {
// 直接执行阻塞IO,会减少可用工作线程
Thread.sleep(1000); // 或者读文件、网络请求等
return new Result();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}

解决方案:

  • 使用ManagedBlocker处理必要的阻塞操作
  • 将IO操作移至单独的线程池
  • 使用CompletableFuture等异步API

5. 问题:过度创建ForkJoinPool实例

// 问题代码 - 每次执行创建新池
public static <T> T processWithForkJoin(ForkJoinTask<T> task) {
// 每次调用创建新的ForkJoinPool
ForkJoinPool pool = new ForkJoinPool(); // 资源浪费
return pool.invoke(task);
// 池没有被关闭,导致资源泄漏
}

解决方案:

  • 重用ForkJoinPool.commonPool()
  • 如需自定义池,使其成为单例或共享实例
  • 不再使用时显式关闭自定义池

1. 使用日志记录任务执行

@Override
protected Integer compute() {
int taskSize = end - start;
String threadName = Thread.currentThread().getName();
logger.debug("Thread {} executing task of size {} ({}-{})",
threadName, taskSize, start, end);
if (taskSize <= THRESHOLD) {
Integer result = computeDirectly();
logger.debug("Thread {} completed direct computation with result {}",
threadName, result);
return result;
}
// 任务分割和递归处理...
logger.debug("Thread {} splitting task {}-{} at mid={}",
threadName, start, end, mid);
// 分别fork和计算...
logger.debug("Thread {} combining results: {} + {} = {}",
threadName, leftResult, rightResult, result);
return result;
}

2. 实现自定义监控机制

public class MonitoredTask<T> extends RecursiveTask<T> {
private final TaskStats stats = new TaskStats();
private final RecursiveTask<T> actual;
public MonitoredTask(RecursiveTask<T> actual) {
this.actual = actual;
}
@Override
protected T compute() {
stats.startTime = System.nanoTime();
stats.threadId = Thread.currentThread().getId();
try {
T result = actual.compute();
stats.completed = true;
return result;
} catch (Exception e) {
stats.exception = e;
stats.completed = false;
throw e;
} finally {
stats.endTime = System.nanoTime();
stats.duration = stats.endTime - stats.startTime;
TaskMonitor.recordTaskStats(stats);
}
}
// 任务统计内部类
static class TaskStats {
long startTime;
long endTime;
long duration;
long threadId;
boolean completed;
Exception exception;
// 更多统计信息...
}
}

3. 使用JMX监控ForkJoinPool

public class ForkJoinPoolMonitor implements ForkJoinPoolMonitorMBean {
private final ForkJoinPool pool;
public ForkJoinPoolMonitor(ForkJoinPool pool) {
this.pool = pool;
}
@Override
public int getParallelism() {
return pool.getParallelism();
}
@Override
public int getPoolSize() {
return pool.getPoolSize();
}
@Override
public int getActiveThreadCount() {
return pool.getActiveThreadCount();
}
@Override
public long getQueuedTaskCount() {
return pool.getQueuedTaskCount();
}
@Override
public long getStealCount() {
return pool.getStealCount();
}
// 注册监控
public static void registerMonitor(ForkJoinPool pool, String name) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName objName = new ObjectName("monitors:type=ForkJoinPool,name=" + name);
ForkJoinPoolMonitor monitor = new ForkJoinPoolMonitor(pool);
mbs.registerMBean(monitor, objName);
}
}

ForkJoinTask作为Java并发编程中的专用工具,为开发者提供了以下核心价值:

  1. 高效并行处理:通过分治算法和工作窃取,优化多核处理器资源利用
  2. 简化并行编程:框架自动处理任务调度、负载均衡和结果合并
  3. 性能提升:特别适合CPU密集型的递归分解任务,可实现接近线性的加速比
  4. 自适应调度:通过工作窃取,自动平衡不同工作线程的负载
  5. 丰富的API:提供了多种子类和工具方法,满足不同场景需求

ForkJoinTask在实际应用中展示了显著价值:

  1. 大数据处理:处理大型数组、集合和数据集的并行操作
  2. 搜索和排序:实现高效的并行排序、查找和过滤
  3. 图像处理:并行处理图像滤镜、转换和分析
  4. 科学计算:矩阵运算、模拟和数值分析
  5. Java平台核心功能
    • Arrays.parallelSort()
    • Stream.parallel()
    • CompletableFuture内部机制

随着计算硬件和并发编程的发展,ForkJoinTask相关技术可能有以下发展趋势:

  1. 进一步简化API:更高级别的抽象,降低使用门槛

  2. 与反应式编程的结合:整合Project Reactor和RxJava等框架

  3. 硬件感知优化:针对现代CPU架构特性(NUMA、缓存一致性)优化

  4. 专用加速器支持:利用GPU、FPGA等专用硬件加速并行计算

  5. **更智能

    • 在真实项目中应用并收集反馈

模式1:基本分治计算(有返回值)

public class BasicRecursiveTask<T> extends RecursiveTask<T> {
private final Problem problem;
private static final int THRESHOLD = 100;
public BasicRecursiveTask(Problem problem) {
this.problem = problem;
}
@Override
protected T compute() {
if (problem.size() <= THRESHOLD) {
return problem.solveDirectly();
}
// 分割问题
Problem[] subProblems = problem.split();
BasicRecursiveTask<T> leftTask = new BasicRecursiveTask<>(subProblems[0]);
BasicRecursiveTask<T> rightTask = new BasicRecursiveTask<>(subProblems[1]);
// 异步执行左子任务
leftTask.fork();
// 同步执行右子任务
T rightResult = rightTask.compute();
// 等待左子任务结果
T leftResult = leftTask.join();
// 合并结果
return problem.combine(leftResult, rightResult);
}
}

模式2:基本分治处理(无返回值)

public class BasicRecursiveAction extends RecursiveAction {
private final Data data;
private final int start;
private final int end;
private static final int THRESHOLD = 1000;
public BasicRecursiveAction(Data data, int start, int end) {
this.data = data;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
data.processRange(start, end);
return;
}
int middle = (start + end) >>> 1;
// 创建并执行子任务
invokeAll(
new BasicRecursiveAction(data, start, middle),
new BasicRecursiveAction(data, middle, end)
);
}
}

模式3:事件完成触发(CountedCompleter)

public class CompletionNotifyingTask<T> extends CountedCompleter<T> {
private final Problem problem;
private final Callback<T> callback;
private T result;
public CompletionNotifyingTask(Problem problem, Callback<T> callback) {
this.problem = problem;
this.callback = callback;
}
@Override
public void compute() {
if (problem.isSmall()) {
result = problem.solve();
tryComplete();
return;
}
List<Problem> subProblems = problem.divide();
setPendingCount(subProblems.size());
for (Problem subProblem : subProblems) {
CompletionNotifyingTask<T> subtask =
new CompletionNotifyingTask<>(subProblem, null);
subtask.fork();
}
}
@Override
public void onCompletion(CountedCompleter<?> caller) {
if (caller != this) {
CompletionNotifyingTask<T> task = (CompletionNotifyingTask<T>)caller;
result = problem.combine(result, task.result);
}
if (callback != null && getCompleter() == null) {
callback.onComplete(result);
}
}
@Override
public T getRawResult() {
return result;
}
}

通用阈值计算工具

/**
* 用于计算任务分解阈值的工具类,根据处理器数量和数据大小动态调整
*/
public class ThresholdCalculator {
// 每个处理器核心能高效处理的最小元素数量(可根据特定任务调整)
private static final int MIN_ELEMENTS_PER_CORE = 1000;
/**
* 计算适合当前处理器和数据集的任务分解阈值
*
* @param dataSize 数据总大小
* @param minThreshold 最小允许阈值(防止过度分割)
* @return 计算出的任务分解阈值
*/
public static int calculate(int dataSize, int minThreshold) {
int processors = Runtime.getRuntime().availableProcessors();
// 基本计算:每个处理器分配一定量的工作
int calculatedThreshold = Math.max(1, dataSize / (processors * 2));
// 确保不低于每核心最小元素数
calculatedThreshold = Math.max(calculatedThreshold, MIN_ELEMENTS_PER_CORE);
// 确保不低于最小阈值
return Math.max(calculatedThreshold, minThreshold);
}
/**
* 根据任务类型获取推荐阈值
*
* @param dataSize 数据总大小
* @param taskType 任务类型
* @return 推荐阈值
*/
public static int getRecommendedThreshold(int dataSize, TaskType taskType) {
switch (taskType) {
case SIMPLE_COMPUTE: return calculate(dataSize, 1000);
case COMPLEX_COMPUTE: return calculate(dataSize, 50);
case MIXED_IO_COMPUTE: return calculate(dataSize, 10);
default: return calculate(dataSize, 500);
}
}
public enum TaskType {
SIMPLE_COMPUTE, // 简单计算(如数组求和)
COMPLEX_COMPUTE, // 复杂计算(如矩阵运算)
MIXED_IO_COMPUTE // 混合计算和IO操作
}
}

ForkJoinPool监控工具

/**
* ForkJoinPool监控工具,收集和报告池的性能指标
*/
public class ForkJoinPoolMonitor {
private final ForkJoinPool pool;
private final ScheduledExecutorService scheduler;
private final long initialDelay;
private final long period;
private final TimeUnit unit;
private final Consumer<PoolStats> statsConsumer;
private ScheduledFuture<?> monitorTask;
public ForkJoinPoolMonitor(ForkJoinPool pool,
long initialDelay,
long period,
TimeUnit unit,
Consumer<PoolStats> statsConsumer) {
this.pool = pool;
this.initialDelay = initialDelay;
this.period = period;
this.unit = unit;
this.statsConsumer = statsConsumer;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
}
/**
* 启动监控
*/
public void start() {
monitorTask = scheduler.scheduleAtFixedRate(
this::collectAndReportStats,
initialDelay, period, unit
);
}
/**
* 停止监控
*/
public void stop() {
if (monitorTask != null) {
monitorTask.cancel(false);
}
scheduler.shutdown();
}
private void collectAndReportStats() {
PoolStats stats = new PoolStats(
pool.getParallelism(),
pool.getPoolSize(),
pool.getActiveThreadCount(),
pool.getQueuedSubmissionCount(),
pool.getQueuedTaskCount(),
pool.getStealCount()
);
statsConsumer.accept(stats);
}
/**
* 池统计信息类
*/
public static class PoolStats {
private final int parallelism;
private final int poolSize;
private final int activeThreads;
private final long queuedSubmissions;
private final long queuedTasks;
private final long steals;
private final long timestamp;
// 构造函数和getter方法
// ...
@Override
public String toString() {
return String.format(
"ForkJoinPool Stats [time=%s, parallelism=%d, size=%d, active=%d, " +
"queued submissions=%d, queued tasks=%d, steals=%d]",
new Date(timestamp), parallelism, poolSize, activeThreads,
queuedSubmissions, queuedTasks, steals);
}
}
}

1. 文件系统并行遍历

/**
* 并行文件系统遍历任务
*/
public class ParallelFileWalker<R> extends RecursiveTask<List<R>> {
private final Path directory;
private final FileVisitor<R> visitor;
private final int depth;
private final int maxDepth;
public ParallelFileWalker(Path directory, FileVisitor<R> visitor, int depth, int maxDepth) {
this.directory = directory;
this.visitor = visitor;
this.depth = depth;
this.maxDepth = maxDepth;
}
@Override
protected List<R> compute() {
List<R> results = new ArrayList<>();
try {
// 处理当前目录中的文件
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
List<ParallelFileWalker<R>> subTasks = new ArrayList<>();
for (Path path : stream) {
if (Files.isDirectory(path)) {
if (depth < maxDepth) {
// 创建子目录任务
ParallelFileWalker<R> subTask =
new ParallelFileWalker<>(path, visitor, depth + 1, maxDepth);
subTasks.add(subTask);
subTask.fork();
}
} else {
// 处理文件
R result = visitor.visitFile(path);
if (result != null) {
results.add(result);
}
}
}
// 处理子任务结果
for (ParallelFileWalker<R> subTask : subTasks) {
results.addAll(subTask.join());
}
}
// 处理当前目录
R dirResult = visitor.visitDirectory(directory);
if (dirResult != null) {
results.add(dirResult);
}
} catch (IOException e) {
visitor.visitError(directory, e);
}
return results;
}
/**
* 文件访问者接口
*/
public interface FileVisitor<R> {
R visitFile(Path file) throws IOException;
R visitDirectory(Path dir) throws IOException;
void visitError(Path path, IOException error);
}
/**
* 静态工厂方法
*/
public static <R> List<R> walk(Path start, FileVisitor<R> visitor, int maxDepth) {
ForkJoinPool pool = new ForkJoinPool();
return pool.invoke(new ParallelFileWalker<>(start, visitor, 0, maxDepth));
}
}

2. 并行文档索引构建器

/**
* 并行文档索引构建器
*/
public class ParallelIndexBuilder extends RecursiveTask<Map<String, List<DocOccurrence>>> {
private final List<Document> documents;
private final int start;
private final int end;
private static final int THRESHOLD = 10; // 每个任务处理的文档数
public ParallelIndexBuilder(List<Document> documents, int start, int end) {
this.documents = documents;
this.start = start;
this.end = end;
}
@Override
protected Map<String, List<DocOccurrence>> compute() {
if (end - start <= THRESHOLD) {
return processDocumentRange();
}
int mid = (start + end) >>> 1;
ParallelIndexBuilder leftTask = new ParallelIndexBuilder(documents, start, mid);
ParallelIndexBuilder rightTask = new ParallelIndexBuilder(documents, mid, end);
leftTask.fork();
Map<String, List<DocOccurrence>> rightResult = rightTask.compute();
Map<String, List<DocOccurrence>> leftResult = leftTask.join();
// 合并两个索引
return mergeIndices(leftResult, rightResult);
}
private Map<String, List<DocOccurrence>> processDocumentRange() {
Map<String, List<DocOccurrence>> index = new HashMap<>();
for (int i = start; i < end; i++) {
Document doc = documents.get(i);
processDocument(doc, index);
}
return index;
}
private void processDocument(Document doc, Map<String, List<DocOccurrence>> index) {
String[] words = doc.getContent().split("\\s+");
for (int pos = 0; pos < words.length; pos++) {
String word = words[pos].toLowerCase()
.replaceAll("[^a-z0-9]", "");
if (word.isEmpty()) continue;
DocOccurrence occurrence = new DocOccurrence(doc.getId(), pos);
index.computeIfAbsent(word, k -> new ArrayList<>())
.add(occurrence);
}
}
private Map<String, List<DocOccurrence>> mergeIndices(
Map<String, List<DocOccurrence>> left,
Map<String, List<DocOccurrence>> right) {
// 将右索引合并到左索引
right.forEach((word, occurrences) -> {
left.computeIfAbsent(word, k -> new ArrayList<>())
.addAll(occurrences);
});
return left;
}
// 文档和位置信息类
public static class DocOccurrence {
private final String docId;
private final int position;
public DocOccurrence(String docId, int position) {
this.docId = docId;
this.position = position;
}
// getter方法
}
// 文档类
public static class Document {
private final String id;
private final String content;
public Document(String id, String content) {
this.id = id;
this.content = content;
}
// getter方法
}
// 构建索引的静态方法
public static Map<String, List<DocOccurrence>> buildIndex(List<Document> documents) {
return new ForkJoinPool().invoke(
new ParallelIndexBuilder(documents, 0, documents.size())
);
}
}

ForkJoinTask作为Java并发编程的核心组件,为解决可分解的并行计算问题提供了强大的支持。它通过轻量级任务抽象和工作窃取算法,有效解决了传统并发编程中的资源利用和负载均衡问题。
我们了解到,合理使用ForkJoinTask需要掌握任务分割粒度、正确的fork-join模式和异常处理技巧。通过与CompletableFuture和传统线程池的对比,明确ForkJoinTask的适用场景和选择依据。
ForkJoinTask不仅是一个API,更是一种并行思维模式的体现。
Happy coding!