J.U.C-Fork/Join框架

Fork/Join 框架

本文主要介绍Fork/Join框架的基本原理、算法、设计思路与实现

1)什么是Fork/Join 框架
2)Fork/Join框架的算法
3)Fork/Join框架核心类以及它们之间的协作
4)Fork/Join使用实例
5)Fork/Join实现原理(未完待续)

参考:http://www.cnblogs.com/wanly3643/p/3992238.html

什么是Fork/Join 框架

Fork/Join 框架是Java7 提供的一个用于执行并行任务的框架,其核心思想就是将一个大的任务分割成若干小任务(前提是这些小任务的运行是互相不依赖的),最终汇总各个小任务结果得到大任务结果的框架。优点类似单机版本的MapReduce。

理解Fork/Join两个单词:Fork就是将一个大任务分割成若干小的计算任务并行执行,Join就是合并这些小任务的执行结果生成大任务结果。下图显示了其运行流程:

Fork/join流程图

Fork/Join框架的算法

前面已经讲了Fork/Join的核心思想就是任务分割以及小任务结果的合并,那么在这个过程中,任务分割之后的是怎么被线程执行的呢?任务是怎么调度的呢?这里主要就是Fork/Join的工作窃取算法。

工作窃取算法:指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下:

这里写图片描述

1)对于一个大的任务我们分割成互相不依赖的N个小任务,放在多个不同的队列中。

2)为每个队列创建一个单独的线程来消费这些任务,也就是线程与队列一一对应。

3)但是上图中,如果线程1很快完成了队列1中的所有任务,而队列2任务还没被消费完,线程1与其等着,不如去窃取队列1里面的任务消费。这时它们会访问同一个队列2,所以为了减少竞争队列都是双端队列。

窃取算法优点:充分利用线程资源进行计算,减少线程之间的竞争。

该算法要点:

  1. 每个Worker线程都维护一个任务队列,即ForkJoinWorkerThread中的任务队列。

  2. 任务队列是双向队列,这样可以同时实现LIFO和FIFO。

  3. 子任务会被加入到原先任务所在Worker线程的任务队列。

  4. Worker线程用LIFO的方法取出任务,也就后进队列的任务先取出来(子任务总是后加入队列,但是需要先执行)。

  5. Worker线程的任务队列为空,会随机从其他的线程的任务队列中拿走一个任务执行(所谓偷任务:steal work,FIFO的方式)。

  6. 如果一个Worker线程遇到了join操作,而这时候正在处理其他任务,会等到这个任务结束。否则直接返回。

  7. 如果一个Worker线程偷任务失败,它会用yield或者sleep之类的方法休息一会儿,再尝试偷任务(如果所有线程都是空闲状态,即没有任务运行,那么该线程也会进入阻塞状态等待新任务的到来)。

Fork/Join框架核心类以及它们之间的协作

Fork/Join框架里面涉及的核心类如下:

(1)ForkJoinPool:这个类实现了ExecutorService接口和工作窃取算法(Work-Stealing Algorithm)。它管理工作线程,并提供任务的状态信息,以及任务的执行信息。

(2)ForkJoinPool.WorkQueue:这个类是支持工作窃取算法和外部任务提交的队列。

(3)ForkJoinWorkerThread:执行任务的工作线程

(4)ForkJoinTask:这个类是一个将在ForkJoinPool中执行的任务的基类,提供fork()和join()操作的机制。但是这个类比较复杂,抽象方法比较多,日常使用时一般不会继承ForkJoinTask来实现自定义的任务,而是继承ForkJoinTask的两个子类:

  • RecursiveAction :子任务不带返回结果时使用
  • RecursiveTask:子任务带返回结果时使用

说一说这些类的关系:

(1)一个ForkJoinPool.WorkQueue 和 一个 ForkJoinWorkerThread一般是绑定的,也就是一个线程对应一个队列。ForkJoinPool.WorkQueue里面保存了很多的ForkJoinTask。

(2)ForkJoinPool.WorkQueue 和 ForkJoinWorkerThread 都属于ForkJoinPool。

(3)ForkJoinPool做线程分配和队列分配。

Fork/Join使用实例

下面以 1到 Integer.MAX_VALUE 自加为例来说明使用Fork/Join框架的优势:

源码如下:

public class ForkJoinTest extends RecursiveTask<Long>{

    private static final long MAX = 1000;
    private long start;
    private long end;

    public ForkJoinTest(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long sum = 0;
        if( (end-start) < MAX ){
            for (long i= start; i<=end; i++){
                sum += i;
            }
        }else {
            long middle = (start+end)/2;
            ForkJoinTest left = new ForkJoinTest(start, middle);
            ForkJoinTest right = new ForkJoinTest(middle+1, end);
            left.fork();
            right.fork();
            long leftRes = left.join();
            long rightRes = right.join();
            sum = leftRes+rightRes;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start1 = System.nanoTime();
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTest test = new ForkJoinTest(1, Integer.MAX_VALUE);
        Future result = pool.submit(test);
        long end1 = System.nanoTime();
        System.out.println(result.get());
        System.out.println(String.format("time1=%s", end1-start1));

        long start2 = System.nanoTime();
        long sum = 0l;
        for (long i=1; i<=Integer.MAX_VALUE; i++){
            sum+=i;
        }
        long end2 = System.nanoTime();
        System.out.println(sum);
        System.out.println(String.format("time2=%s", end2-start2));
    }
}

将任务按照自加维度划分,如果范围小于1000,就可以直接执行,否则切分任务。

if 任务计算的维度在设计范围内{
    执行小任务
}
else{
    更加细粒度划分任务为小任务,并fork小任务
}

上面实例运行结果是:

// fork/join 执行
2305843008139952128
time1=3401308
// 单线程执行
2305843008139952128
time2=813508831

从结果可知,在维度划分到1000级别时,使用fork/join速度比单线程高了两个数量级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值