11. Fork/Join

7 篇文章 0 订阅

11. Fork/Join

11.1 Fork/Join框架简介

 Fork/Join它可以将一个大任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并输出。Fork/Join 框架要完成两件事情:

  • Fork:把一个复杂任务进行拆分,大事化小。
  • Join:把拆分的结果进行合并。

ForkJoin逻辑图.png

  1. **任务分隔:**首先Fork/Join框架需要把大的任务分割为足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割。
  2. **执行任务合并结果:**分割子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完成的结果都放在另一个队列里,启动一个队列从队列里取数据,然后侯合并这些数据。

​ 在Java的Fork/Join框架中,使用两个类完成上述操作

  1. **ForkJoinTask:**要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行的fork和Join的机制。通常情况下不需要直接集成ForkJoinTask类:

    1. RecursiveAtion:用于没有返回结果的任务。
    2. RecursiveTask:用于有返回结果的任务。
  2. ForJoinPool:ForJoinTask需要通过ForkJoinPool来执行

  3. RecursiveTask: 继承后可以实现递归调用的任务。

    11.2 Fork/Join框架的实现原理

     ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForJoinTask数组负责将存放以及将程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。

    11.2.1 关系图

    ForkJoin类图.png

分支合并池 类比=> 线程池

RecursiveTask类图.png

递归任务:继承后可以实现递归调用任务

11.2.2 Fork方法的实现原理

 当我们调用ForkJoinTask的fork方法时,程序会把任务放在ForkJoinWorkThread中pushTask的workQueQue中,异步地执行这个任务,然后立即返回结果。

    public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }

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

       final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; ForkJoinPool p;
            int b = base, s = top, n;
            if ((a = array) != null) {    // ignore if queue removed
                int m = a.length - 1;     // fenced write for task visibility
                U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
                U.putOrderedInt(this, QTOP, s + 1);
                if ((n = s - b) <= 1) {
                    if ((p = pool) != null)
                        p.signalWork(p.workQueues, this);
                }
                else if (n >= m)
                    growArray();
            }
        }

11.3 join方法

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

    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }

 它首先调用doJoin方法,通过doJoin方法来的带当前状态来判断返回什么结果,任务状态有4种。

  • 已完成(NORMAL):直接返回任务结果

  • 被取消(CANCELLED):直接抛出CancellationException

  • 信号(SIGNAL)

  • 出现异常(EXCEPTIONAL) 直接抛出对应的异常

        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();
        }
        final int doExec() {
            int s; boolean completed;
            if ((s = status) >= 0) {
                try {
                    completed = exec();
                } catch (Throwable rex) {
                    return setExceptionalCompletion(rex);
                }
                if (completed)
                    s = setCompletion(NORMAL);
            }
            return s;
        }
    

    在doJoin()方法流程如下:

    1. 首先通过查看任务状态,看任务是否已经执行完成,如果执行完成,则直接返回任务状态;

    2. 如果没有完成,则从数组里去除任务并执行。

    3. 如果任务顺利完成,则设置任务状态为NORMAL,如果出现异常,则记录异常,并将任务状态设置为EXCEPTIONAL。

11.4 Fork/Join框架异常处理

 ForkJoinTask在执行的时候可能会抛出异常,但是我们没有办法在主线程里直接捕获异常,所以ForkJoinTask提供了isCompletedAbnomally()方法来检测任务是否抛出异常已经被取消了,并且可以通过ForkJoinTask的getException()方法来获取异常。

 getException()方法返回Throwable对象,如果任务被取消则返回CancellationException。如果任务没有完成或者没有抛出异常则返回null。

11.5 案例

生成一个计算任务,计算1+2+3…+1000,每100个数分一个子任务

class MyTask extends RecursiveTask<Integer> {

    // 拆分差值不能超过10
    private static final Integer VALUE = 10;
    // 拆分开始
    private int begin;
    // 拆分结束值
    private int end;
    // 返回结果
    private int result;

    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    // 拆分和合并过程
    @Override
    protected Integer compute() {
        // 判断相加两个值是否大于10
        if (end - begin <= VALUE) {
            // 相加操作
            for (int i = begin; i <= end; i++) {
                result = result + i;
            }
        } else {// 进一步拆分
            int middle = (begin + end) / 2;
            // 拆分左边
            MyTask task01 = new MyTask(begin, middle);
            MyTask task02 = new MyTask(middle + 1, end);
            // 调用方法拆分
            task01.fork();
            task02.fork();
            // 合并
            result = task01.join() + task02.join();
        }
        return result;
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建MyTask对象
        MyTask task = new MyTask(0, 100);
        // 创建分支合并持对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(task);
        Integer result = forkJoinTask.get();
        System.out.println(result);
        forkJoinPool.shutdown();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值