本文目录导读:

- 核心思想:分而治之 + 工作窃取
- 关键组件
- 计算步骤详解(以 RecursiveTask 求和为例)
- 关键机制:工作窃取 (Work-Stealing)
- 数学模型:如何计算并行效率?
- 总结:ForkJoinPool 计算流程图解
ForkJoinPool 分治任务的计算主要依赖于 工作窃取算法 和 分治递归 两大核心机制,下面为你详细拆解其计算过程。
核心思想:分而治之 + 工作窃取
- 分治 (Divide-and-Conquer):将一个大任务递归地拆分成足够小的子任务,直到子任务可以直接求解,然后合并子任务的结果。
- 工作窃取 (Work-Stealing):如果一个线程的队列中没有任务了,它会从其他线程的队列尾部“偷”一个任务来执行,以保持所有线程的忙碌状态,实现真正的并行。
关键组件
要理解计算过程,需要先认识几个核心类:
ForkJoinPool:线程池本身,管理着一组工作线程。ForkJoinTask:提交给线程池的任务,它有两个常用子类:RecursiveAction:用于没有返回结果的任务(对一个数组进行并行排序)。RecursiveTask<V>:用于有返回结果的任务(计算1加到10000)。
ForkJoinWorkerThread:线程池中的工作线程,每个线程维护一个自己的双端队列(Deque),用于存放分配给它的子任务。
计算步骤详解(以 RecursiveTask 求和为例)
假设我们想用并行计算 1 + 2 + 3 + ... + 10000。
任务定义与拆分
你首先需要创建一个类继承 RecursiveTask<Long>,并实现 compute() 方法。
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000; // 拆分阈值
private int start;
private int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 情况1:任务足够小,直接计算
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i <= end; i++) sum += i;
return sum;
}
// 情况2:任务太大,进行拆分
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(start, mid);
SumTask rightTask = new SumTask(mid + 1, end);
// 关键步骤:分叉 (Fork)
leftTask.fork(); // 1. 将左子任务提交到线程池,让它异步执行
Long rightResult = rightTask.compute(); // 2. 本线程自己计算右子任务
Long leftResult = leftTask.join(); // 3. 等待左子任务完成并获取结果
// 合并 (Join)
return leftResult + rightResult;
}
}
提交与执行流程
ForkJoinPool pool = new ForkJoinPool(); // 使用默认并行度 SumTask rootTask = new SumTask(1, 10000); // 创建根任务 Long result = pool.invoke(rootTask); // 提交并等待结果 System.out.println(result); // 输出: 50005000
内部具体执行过程(假设有4个线程):
- 任务提交:
pool.invoke(rootTask)将根任务(1-10000)放到线程池的提交队列中。 - 线程拾取:一个空闲线程(假设是 Thread-1)从提交队列中取出根任务,执行它的
compute()方法。 - 第一次拆分:
- Thread-1 发现任务太大 (10000 > 1000),于是将其拆分为
Left (1-5000)和Right (5001-10000)。 leftTask.fork():将Left任务放入 Thread-1 自己的 工作队列头部,这相当于说:“我稍后会处理你,或者让别人偷走”。rightTask.compute():Thread-1 立即开始计算Right任务。
- Thread-1 发现任务太大 (10000 > 1000),于是将其拆分为
- 递归拆分与并行计算:
- Thread-1 在计算
Right (5001-10000)时,发现还是太大,再次拆分:- 拆分出
Left (5001-7500)和Right (7501-10000)。
- 拆分出
leftTask.fork():将Left (5001-7500)放入 Thread-1 的队列头部。rightTask.compute():Thread-1 开始计算Right (7501-10000),发现依然太大,继续拆分... 直到任务变得足够小。
- Thread-1 在计算
- 工作窃取 (关键):
- 假设:Thread-2 一直空闲,它的队列是空的。
- 偷取:Thread-2 检查其他线程的队列,它发现 Thread-1 的队列中有很多未完成的任务(如
Left (1-5000),Left (5001-7500)等)。 - 窃取:Thread-2 从 Thread-1 的队列尾部偷走了
Left (5001-7500)这个任务。(注意:从尾部偷是为了减少与队列头部本线程任务的竞争)。 - 执行:Thread-2 开始独立执行偷来的
Left (5001-7500),它也会递归地拆分、fork、compute,将自己拆出的子任务放入自己的队列。
- 结果合并(Join):
- 当 Thread-1 计算完一个叶子任务(7501-8500)后,开始层层回溯。
- 当它需要合并时,会调用
leftTask.join()。 leftTask恰好被 Thread-2 偷走了,Thread-1 的join()会阻塞等待,直到 Thread-2 完成计算并返回结果。- 当所有子任务都完成时,最外层的
join()返回最终结果50005000。
关键机制:工作窃取 (Work-Stealing)
这是 ForkJoinPool 最核心的设计亮点,它解决了传统线程池的一个痛点。
- 传统线程池:所有任务放入一个公共队列,线程从头部取任务,如果某个线程的任务非常大(比如计算斐波那契数列的第50项),它就会一直占据线程,而其他线程可能没事干。
- ForkJoinPool 的解法:
- 每个线程有自己的双端队列 (Deque),本线程放入自己的任务(
fork)是放到队头,执行时(如果是连续fork和compute)也是从队头取任务,这保证了 LIFO(后进先出),即最近放入的任务最先被处理,这种局部性有利于利用 CPU 缓存。 - 空闲线程会扫描其他线程的队列,从队尾偷取任务,这保证了空闲线程能找到活干,并且由于是从尾部偷(通常是较大的、更早放入的任务),不容易与队列主人的操作(都在头部)产生竞争。
- 每个线程有自己的双端队列 (Deque),本线程放入自己的任务(
工作窃取的优势
- 负载均衡:计算量大的线程会被其他线程“帮助”,自动实现负载均衡,无需手动调整。
- 减少线程切换:因为每个线程大部分时间在处理自己队列头部的任务,而窃取发生在队列尾部,大大减少了锁的竞争。
- 提高 CPU 利用率:尽可能让所有线程都处于忙碌状态。
数学模型:如何计算并行效率?
ForkJoinPool 的计算效率可以用 Amdahl定律 (阿姆达尔定律) 来理解:
[ \text{加速比}(S) = \frac{1}{(1 - P) + \frac{P}{N}} ]
P:任务的可并行化比例(即通过fork()分配出去的部分)。N:CPU 核心数(或线程数)。
应用到这个求和场景:
- 串行部分:任务拆分、结果的合并(
join())、线程调度,这些开销不可忽略,尤其是当任务非常小时。 - 并行部分:各个子任务的计算(
compute()中的循环求和)。 - 关键点:需要找到一个合适的
THRESHOLD(拆分阈值)。- 阈值太大:并行度不足,有些线程可能空闲。
- 阈值太小:拆分和合并的开销(串行部分)会远大于计算本身,导致实际速度反而可能比单线程更慢(甚至会出现负收益)。
经验公式 / 实验结果: 通常建议阈值大小是 100 到 10000 个元素 之间,具体依赖计算复杂度,可以通过性能测试来确定最佳阈值。
ForkJoinPool 计算流程图解
用户提交任务 (ForkJoinPool.invoke)
│
▼
初始任务 (Root Task)
│
│ compute()
▼
┌───────────────────┐
│ 判断任务是否足够小? │
└─────────┬─────────┘
│
是 │ 否 (继续拆分)
◄─────┘
│
▼
直接计算 ┌─── leftTask.fork() ───→ 放入本线程队列头部
返回结果 │ │
┌─────┴──────┐ (可能被其他线程窃取)
│ │
▼ ▼
rightTask. leftTask.join()
compute() (等待结果)
│ │
└─────┬──────┘
▼
合并结果 (return)
ForkJoinPool 计算的核心在于 通过递归拆分产生大量粒度合适的子任务,并利用工作窃取机制让空闲线程“帮助”忙碌线程,从而在有限的计算资源下最大化并行吞吐量。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播