JUC系列(十) | Fork&Join框架 并行处理任务

简介: JUC系列(十) | Fork&Join框架 并行处理任务

QQ截图20220525190139.png


多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!!

沉下去,再浮上来,我想我们会变的不一样的。


🚤Fork&Join框架


1)介绍


Fork/Join框架是从Java1.7开始提供的一个并行处理任务的框架,它的基本思路是将一个大任务分解成若干个小任务,并行处理多个小任务,最后再汇总合并这些小任务的结果便可得到原来的大任务结果。


通俗点说它就是一个事情划分给好几个人做,效率得到显著提升


微信截图_20220525190252.png


1、Fork :递归式的将大任务分割成合适大小的小任务。


2、Join:执行任务并合并结果。


2)相关类


我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。 ForkJoin 类提供了在任务中执行 fork 和 join 的机制。


通常情况下我们都是直接继承ForkJoinTask 的子类,Fork/Join框架提供了两个子类:


  • RecursiveAction:一个递归无结果的ForkJoinTask(没有返回值)任务


  • RecursiveTask:一个递归有结果的ForkJoinTask(有返回值)任务


  • ForkJoinTask 主要方法:


fork()   // 在当前线程运行的线程池中安排一个异步执行。简单的理解就是再创建一个子任务。
join()    //当任务完成的时候返回计算结果。
invoke()    //开始执行任务,如果必要,等待计算完成。


ForkJoinPool:另外ForkJoinTask需要通过 ForkJoinPool 来执行


RecursiveTask:一个递归有结果的ForkJoinTask(有返回值)任务


QQ截图20220525190358.png


微信截图_20220525190432.png


微信截图_20220525190510.png


🚁Fork 方法


调用fork方法时,程序会将新创建的子任务放入当前线程的workQueue队列中,Fork/Join框架将根据当前正在并发执行的ForkJoinTask任务的ForkJoinWorkerThread线程状态,来决定是让这个任务在队列中等待,还是创建一个新的ForkJoinWorkerThread线程运行它,又或者是唤起其它正在等待任务的ForkJoinWorkerThread线程运行它。


public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        //将给定的任务添加到提交者当前队列的提交队列中,如果为 null 或存在竞争,则创建一个。
        ForkJoinPool.common.externalPush(this);
    return this;
}


push方法把当前任务存放在 ForkJoinTask 数组队列里。然后再调用 ForkJoinPool 的 signalWork()方法唤醒或创建一个工作线程来执行任务。代码如下:


//推送任务。 仅由非共享队列中的所有者调用。
final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a;
    int s = top, d, cap, m;
    ForkJoinPool p = pool;
    if ((a = array) != null && (cap = a.length) > 0) {
        QA.setRelease(a, (m = cap - 1) & s, task);
        top = s + 1;
        if (((d = s - (int)BASE.getAcquire(this)) & ~1) == 0 &&
            p != null) {                 // size 0 or 1
            VarHandle.fullFence();
            // signalWork方法的意义在于,如果运行的工作程序太少,则尝试创建或释放工作程序。
            p.signalWork(); 
        }
        // 如果array的剩余空间不够了,则进行增加
        else if (d == m)
            growArray(false); 
    }
}


🪂Join  方法


Join 方法的主要作用是阻塞当前线程并等待获取结果。代码如下:


通过 doJoin()方法得到当前任务的状态来判断返回什么结果,任务状态有 4 种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常(EXCEPTIONAL)


public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        //假如任务状态是抛出异常状态,就直接抛出对应的异常
        //若是任务状态是被取消状态,则直接抛出CancellationException异常。
        reportException(s);
    //如若任务状态是已经完成,则直接立马返回任务结果。
    //即使此任务异常完成,如果不知道此任务已完成,则返回null 
    return getRawResult();
}


让我们分析一下 doJoin 方法的实现


private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    //1. 首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成,则直接返回任务状态;
  //2. 如果没有执行完任务,则从任务数组里取出任务并执行。
    //3. 如果任务顺利执行完成,则设置任务状态为 NORMAL,假如出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL。
    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();
}


🚤Fork/Join 框架的异常处理


ForkJoinTask 在执行的时候可能会抛出异常,但因为它并不是在主线程中运行,故此没有办法在主线程中去捕获异常,这种问题既然是历史遗留, ForkJoinTas这个后来者当然也是提供了API来处理的啦,如下:


//如果此任务引发异常或被取消,则返回true 。 通常用来判断任务情况
public final boolean isCompletedAbnormally() {
    return (status & ABNORMAL) != 0;
}


//getException 方法返回 Throwable 对象,如果任务被取消了则返回CancellationException,如果任务没有完成或者没有抛出异常则返回 null。
public final Throwable getException() {
    int s = status;
    return ((s & ABNORMAL) == 0 ? null :
            (s & THROWN)   == 0 ? new CancellationException() :
            getThrowableException());
}


🌍入门案例


场景: 生成一个计算任务,计算 1+2+3.........+1000,每 100 个数切分一个 子任务


import java.util.concurrent.RecursiveTask;
/**
* 递归累加
*/
public class TaskExample extends RecursiveTask<Long> {
    private int start;
    private int end;
    private long sum;
    /**
  * 构造函数
  * @param start
  * @param end
  */
    public TaskExample(int start, int end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        System.out.println("任务" + start + "=========" + end + "累加开始");
        //大于 100 个数相加切分,小于直接加
        if(end - start <= 100){
            for (int i = start; i <= end; i++) {
                //累加
                sum += i;
            }
        }else {
            //切分为 2 块
            int middle = start + 100;
            //递归调用,切分为 2 个小任务
            TaskExample taskExample1 = new TaskExample(start, middle);
            TaskExample taskExample2 = new TaskExample(middle + 1, end);
            //执行:异步
            taskExample1.fork();
            taskExample2.fork();
            //同步阻塞获取执行结果
            sum = taskExample1.join() + taskExample2.join();
        }
        //加完返回
        return sum;
    }
}


import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
/**
* 分支合并案例
*/
public class ForkJoinPoolDemo {
    /**
 * 生成一个计算任务,计算 1+2+3.........+1000
 * @param args
 */
    public static void main(String[] args) {
        //定义任务
        TaskExample taskExample = new TaskExample(1, 1000);
        //定义执行对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //加入任务执行
        ForkJoinTask<Long> result = forkJoinPool.submit(taskExample);
        //输出结果
        try {
            System.out.println(result.get());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            forkJoinPool.shutdown();
        }
    }
}


🌈自言自语


最近又开始了JUC的学习,感觉Java内容真的很多,但是为了能够走的更远,还是觉得应该需要打牢一下基础。


最近在持续更新中,如果你觉得对你有所帮助,也感兴趣的话,关注我吧,让我们

一起学习,一起讨论吧。


你好,我是博主宁在春,Java学习路上的一颗小小的种子,也希望有一天能扎根长成苍天大树。


希望与君共勉😁


我们:待别时相见时,都已有所成


目录
相关文章
|
6月前
|
并行计算 算法 Java
探索Java并发编程:Fork/Join框架的深度解析
【5月更文挑战第29天】在多核处理器普及的时代,有效利用并发编程以提升程序性能已经成为开发者必须面对的挑战。Java语言提供的Fork/Join框架是一个强大的工具,它旨在利用多线程执行分而治之的任务。本文将通过深入分析Fork/Join框架的工作原理、关键特性以及与传统线程池技术的差异,帮助开发者更好地掌握这一高效处理并发任务的技术手段。
|
6月前
|
并行计算 安全 算法
探索Java并发编程:Fork/Join框架的应用与优化
在多核处理器普及的今天,并发编程已经成为提高程序性能的重要手段。Java提供了多种并发工具,其中Fork/Join框架是处理分治任务的强大工具。本文将深入探讨Fork/Join框架的核心原理、使用场景以及性能优化技巧,帮助开发者更好地利用这一框架解决实际问题。通过实例分析,我们将看到如何有效地使用Fork/Join框架来加速计算密集型任务,并提供一系列最佳实践,以确保高效和线程安全的并发执行。
|
6月前
|
分布式计算 并行计算 Java
探索Java并发编程:Fork/Join框架的应用与实践
【2月更文挑战第18天】在多核处理器时代,为了充分利用计算资源,并发编程成为开发者必备技能。Java提供了多种并发工具,其中Fork/Join框架是处理分而治之问题的有效手段。本文将深入探讨Fork/Join框架的原理、使用场景和实践技巧,帮助读者提升Java并发编程能力。
79 6
|
6月前
|
并行计算 算法 Java
Java并发 -- Fork/Join框架
Java并发 -- Fork/Join框架
61 0
|
6月前
|
NoSQL Java 程序员
多线程并发之线程池Executor与Fork/Join框架
多线程并发之线程池Executor与Fork/Join框架
90 0
|
分布式计算 算法 Java
【JUC基础】16. Fork Join
“分而治之”一直是一个非常有效的处理大量数据的方法。著名的MapReduce也是采取了分而治之的思想。。简单地说,就是如果你要处理 1000 个数据,但是你并不具备处理 1000个数据的能力,那么你可以只处理其中的 10 个,然后分阶段处理 100 次,将 100 次的结进行合成,就是最终想要的对原始 1000 个数据的处理结果。而这就是Fork Join的基本思想。
109 1
|
算法
Fork/Join框架的学习和浅析
Fork/Join框架的学习和浅析
82 0
|
并行计算 算法 Java
【JAVA并发编程专题】Fork/Join框架的理解和使用
【JAVA并发编程专题】Fork/Join框架的理解和使用
Java——多线程高并发系列之Fork/Join框架简单应用
Java——多线程高并发系列之Fork/Join框架简单应用
Java——多线程高并发系列之Fork/Join框架简单应用
|
机器学习/深度学习 分布式计算 并行计算
面试官:说说你对Fork/Join的并行计算框架的了解?
面试官:说说你对Fork/Join的并行计算框架的了解?
面试官:说说你对Fork/Join的并行计算框架的了解?