Fork/Join框架阅读笔记[通俗易懂]

Fork/Join框架阅读笔记[通俗易懂]什么是Fork/Join框架Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干 个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 我们再通过Fork和Join这两个单词来理解一下Fork/Join框架。Fork就是把一个大任务切分 为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结 果。比如计算1+2+…+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和, 最终汇总这10个子任务的结果。Fork/

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

什么是Fork/Join框架

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干 个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 我们再通过Fork和Join这两个单词来理解一下Fork/Join框架。Fork就是把一个大任务切分 为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结 果。比如计算1+2+…+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和, 最终汇总这10个子任务的结果。Fork/Join的运行流程如图6-6所示。
在这里插入图片描述

工作窃取算法

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。那么,为什么 需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,可以把这个任务分割为若干 互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个 队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如A线程负责处理A 队列里的任务。但是,有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有 任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列 里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被 窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿 任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。 工作窃取的运行流程如图6-7所示。
在这里插入图片描述
图6-7 工作窃取运行流程图 工作窃取算法的优点:充分利用线程进行并行计算,减少了线程间的竞争。
工作窃取算法的缺点:在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并 且该算法会消耗了更多的系统资源,比如创建多个线程和多个双端队列。
6.4.3 Fork/Join框架的设计 我们已经很清楚Fork/Join框架的需求了,那么可以思考一下,如果让我们来设计一个 Fork/Join框架,该如何设计?这个思考有助于你理解Fork/Join框架的设计。 步骤1 分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还 是很大,所以还需要不停地分割,直到分割出的子任务足够小。 步骤2 执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分 别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程 从队列里拿数据,然后合并这些数据。 Fork/Join使用两个类来完成以上两件事情。
①ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务 中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继 承它的子类,Fork/Join框架提供了以下两个子类。
·RecursiveAction:用于没有返回结果的任务。 ·RecursiveTask:用于有返回结果的任务。
②ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行。 任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当 一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任 务。

6.4.4
使用Fork/Join框架 让我们通过一个简单的需求来使用Fork/Join框架,需求是:计算1+2+3+4的结果。 使用Fork/Join框架首先要考虑到的是如何分割任务,如果希望每个子任务最多执行两个 数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以Fork/Join框架会把这个任 务fork成两个子任务,子任务一负责计算1+2,子任务二负责计算3+4,然后再join两个子任务 的结果。因为是有结果的任务,所以必须继承RecursiveTask,实现代码如下。

package fj; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.ForkJoinPool; 
import java.util.concurrent.Future; 
import java.util.concurrent.RecursiveTask; 
public class CountTask extends RecursiveTask<Integer> { 
    
private static final int THRESHOLD = 2; // 阈值 
private int start; 
private int end; public CountTask(int start, int end) { 
    
this.start = start; 
this.end = end; }
@Override 
protected Integer compute() 
{ 
    int sum = 0; // 如果任务足够小就计算任务 boolean canCompute = (end - start) <= THRESHOLD;
if (canCompute) 
{ 
    
for (int i = start; i <= end; i++) { 
    sum += i; } 
} 
else { 
   // 如果任务大于阈值,就分裂成两个子任务计算 
int middle = (start + end) / 2; 
CountTask leftTask = new CountTask(start, middle); 
CountTask rightTask = new CountTask(middle + 1, end); // 执行子任务 
leftTask.fork(); 
rightTask.fork(); // 等待子任务执行完,并得到其结果 int leftResult=leftTask.join(); 
int rightResult=rightTask.join(); // 合并子任务 sum = leftResult + rightResult; }
return sum; }
public static void main(String[] args) 
{ 
    ForkJoinPool forkJoinPool = new ForkJoinPool(); // 生成一个计算任务,负责计算1+2+3+4
 CountTask task = new CountTask(1, 4); // 执行一个任务 
 Future<Integer> result = forkJoinPool.submit(task); try 
 { 
    System.out.println(result.get()); } 
 catch (InterruptedException e) { 
    } 
 catch (ExecutionException e) { 
    } } 
 }

通过这个例子,我们进一步了解ForkJoinTask,ForkJoinTask与一般任务的主要区别在于它 需要实现compute方法,在这个方法里,首先需要判断任务是否足够小,如果足够小就直接执 行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用fork方法时,又会进入 compute方法,看看当前子任务是否需要继续分割成子任务,如果不需要继续分割,则执行当 前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。

6.4.6 Fork/Join框架的实现原理 ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责 将存放程序提交给ForkJoinPool的任务,而ForkJoinWorkerThread数组负责执行这些任务。 (1)ForkJoinTask的fork方法实现原理 当我们调用ForkJoinTask的fork方法时,程序会调用ForkJoinWorkerThread的pushTask方法 异步地执行这个任务,然后立即返回结果。代码如下。 public final ForkJoinTask fork() { ((ForkJoinWorkerThread) Thread.currentThread()) .pushTask(this); return this; }pushTask方法把当前任务存放在ForkJoinTask数组队列里。然后再调用ForkJoinPool的 signalWork()方法唤醒或创建一个工作线程来执行任务。代码如下。

 final void pushTask(ForkJoinTask<> t) { 
    ForkJoinTask<>[] q; 
 int s, m; 
 if ((q = queue) != null) { 
    // ignore if queue removed 
 long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE; 
 UNSAFE.putOrderedObject(q, u, t); 
 queueTop = s + 1; // or use putOrderedInt 
 if ((s -= queueBase) <= 2) 
 pool.signalWork(); 
 else if (s == m) growQueue(); } }2)ForkJoinTask的join方法实现原理 Join方法的主要作用是阻塞当前线程并等待获取结果。让我们一起看看ForkJoinTask的join 方法的实现,代码如下。
public final V join() { 
    
if (doJoin() != NORMAL) 
return reportResult(); 
elsereturn getRawResult(); }
private V reportResult() { 
    
int s; Throwable ex; 
if ((s = status) == CANCELLED) 
throw new CancellationException(); 
if (s == EXCEPTIONAL && (ex = getThrowableException()) != null) 
UNSAFE.throwException(ex); 
return getRawResult(); } 

首先,它调用了doJoin()方法,通过doJoin()方法得到当前任务的状态来判断返回什么结 果,任务状态有4种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常 (EXCEPTIONAL)。 ·如果任务状态是已完成,则直接返回任务结果。 ·如果任务状态是被取消,则直接抛出CancellationException。 ·如果任务状态是抛出异常,则直接抛出对应的异常。 让我们再来分析一下doJoin()方法的实现代码。 private int doJoin() { Thread t; ForkJoinWorkerThread w; int s; boolean completed; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) { if ((s = status) < 0) return s; if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) { try {completed = exec(); } catch (Throwable rex) { return setExceptionalCompletion(rex); }if (completed) return setCompletion(NORMAL); }return w.joinTask(this); }elsereturn externalAwaitDone(); }
在doJoin()方法里,首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成, 则直接返回任务状态;如果没有执行完,则从任务数组里取出任务并执行。如果任务顺利执行 完成,则设置任务状态为NORMAL,如果出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/168843.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月9日 上午11:00
下一篇 2022年8月9日 上午11:16


相关推荐

  • Android.mk的用法和基础 && m、mm、mmm编译命令「建议收藏」

    Android.mk的用法和基础 && m、mm、mmm编译命令「建议收藏」一个Android.mkfile用来向编译系统描述你的源代码。具体来说:该文件是GNUMakefile的一小部分,会被编译系统解析一次或多次。你可以在每一个Android.mkfile中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。选项参考以下文件:build/core/config.mk,默认的值在以下文件中定义:build/core/base_rules.mk。编译系统为

    2022年5月26日
    137
  • 新浪微博模仿的是_微博随便看看在哪

    新浪微博模仿的是_微博随便看看在哪这几天学会了ListView组件,希望能帮到你们。    程序测试图如下:       1.代码如下:   MainActivity.java  packagecom.example.weibokankan;importjava.util.ArrayList;importjava.util.List;

    2025年7月23日
    5
  • OpenSearch 讲解

    OpenSearch 讲解文章目录什么是 OpenSearchOp 和 ElasticSearc 对比 opensearchEl 对比数据准备 OpenSearch 的创建我们对 OpenSearch 的使用 API 分类搜索方式目前在用的产品遇到的问题什么是 OpenSearch 开放搜索 OpenSearch 是一款结构化数据搜索托管服务 为移动应用开发者和网站站长提供简单 高效 稳定

    2026年3月20日
    3
  • 动态规划之01背包问题(最易理解的讲解)[通俗易懂]

    动态规划之01背包问题(最易理解的讲解)[通俗易懂]01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻。01背包的状态转换方程 f[i,j]=Max{f[i-1,j-Wi]+Pi(j>=Wi), f[i-1,j]}f[i,j]表示在前i件物品中选择若干件放在承重为j的背包中,可以取得的最大价值。Pi表示第i件物

    2022年7月26日
    5
  • Application received signal SIGABRT[通俗易懂]

    ApplicationreceivedsignalSIGABRT(null)(( 0CoreFoundation0x0000000182bbadc8<redacted>+148 1libobjc.A.dylib0x000000018221ff80ob…

    2022年4月8日
    110
  • 三角形外心与垂心

    三角形外心与垂心转载请注明出处 谢谢 http blog csdn net acm cxlove article details nbsp nbsp nbsp nbsp nbsp nbsp by cxlovePOJ132 nbsp CircleThroug poj org problem id 1329 题目 就是求外心 但是要输出外接圆方程 输出真 DT include incl

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号