Java并行计算框架Fork/Join

来源:互联网 发布:淘宝买流量没有退款项 编辑:程序博客网 时间:2024/05/18 00:57

0.本文目录

  • 本文目录
  • 开篇明志
  • 什么是ForkJoin计算框架
  • 工作窃取Work stealing
  • Fork-Join框架结构
  • 工作原理
  • 使用ForkJoin
    • 基本用法
  • 参考文献

1.开篇明志

这两天在做阿里中间件的比赛,在看并发的一些内容, 本文将总结一下自己看的Java中Fork/Join计算框架。Fork/Join框架被设计成可以容易地将算法并行化、分治化。在实际情况中,很多时候我们都需要面对经典的“分治”问题。要解决这类问题,主要任务通常被分解为多个任务块(分解阶段),其后每一小块任务被独立并行计算。一旦计算任务完成,每一快的结果会被合并或者解决(解决阶段)。

2.什么是Fork/Join计算框架?

Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。


ForkJoinTask有两个主要的方法:

  • fork () – 这个方法决定了ForkJoinTask的异步执行,凭借这个方法可以创建新的任务。
  • join () – 该方法负责在计算完成侯返回结果,因此允许一个任务等待另一任务执行完成。

这里写图片描述

3.工作窃取(Work stealing)

fork-join 框架通过一种称作工作窃取(work stealing) 的技术减少了工作队列的争用情况。每个工作线程都有自己的工作队列,这是使用双端队列(或者叫做 deque)来实现的(Java 6 在类库中添加了几种 deque 实现,包括 ArrayDequeLinkedBlockingDeque)。当一个任务划分一个新线程时,它将自己推到 deque 的头部。当一个任务执行与另一个未完成任务的合并操作时,它会将另一个任务推到队列头部并执行,而不会休眠以等待另一任务完成(像 Thread.join() 的操作一样)。当线程的任务队列为空,它将尝试从另一个线程的 deque 的尾部 窃取另一个任务。

Fork/Join框架在java.util.concurrent包中加入了两个主要的类:

  • ForkJoinPool
  • ForkJoinTask

ForkJoinPool类是ForkJoinTask实例的执行者,ForkJoinPool的主要任务就是”工作窃取”,其线程尝试发现和执行其他任务创建的子任务。

这里写图片描述

从双端队列base后端进行窃取任务到 worker thread 队列

这里写图片描述

与标准队列相比,deque 具有两方面的优势:减少争用和窃取。因为只有工作线程会访问自身的 deque 的头部,deque 头部永远不会发生争用;因为只有当一个线程空闲时才会访问 deque 的尾部,所以也很少存在线程的 deque 尾部的争用(在 fork-join 框架中结合 deque 实现会使这些访问模式进一步减少协调成本)。跟传统的基于线程池的方法相比,减少争用会大大降低同步成本。此外,这种方法暗含的后进先出(last-in-first-out,LIFO 后进先出)任务排队机制意味着最大的任务排在队列的尾部,当另一个线程需要窃取任务时,它将得到一个能够分解成多个小任务的任务,从而避免了在未来窃取任务。因此,工作窃取实现了合理的负载平衡,

4. Fork-Join框架结构

Fork-Join框架的主要类图和继承关系如下:

这里写图片描述

Fork-Join框架的核心是ForkJoinPool类,它是对AbstractExecutorService类的扩展。ForkJoinPool实现了工作窃取算法,并可以执行ForkJoinTask任务。

5.工作原理

  • 第一、分割任务,只要任务的粒度超过阀值,就不停地将任务分拆为小任务;
  • 第二、执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里。再启动一个线程合并结果队列的值。

Fork-Join框架涉及的主要类如下:

这里写图片描述

Fork/Join使用两个类来完成上面两步:

  • ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:

    • RecursiveAction:用于没有返回结果的任务。
    • RecursiveTask :用于有返回结果的任务,通过泛型参数设置计算的返回值类型。
  • ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。(需要实现compute方法)。


6.使用Fork/Join

基本用法

fork/join首先会做一个工作分割的工作。

if (如果工作量足够小)     直接处理else     将工作量分割为两部分     调用两部分,等待结果返回     整合两部分返回结果

分割工作量这部分代码在ForkJoinTask子类中实现就可以。可以是 RecursiveTask 或者 RecursiveAction.


下面实现一个这样的任务:计算1+2+…+10000 累加任务。

这里写图片描述

import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.Future;import java.util.concurrent.RecursiveTask;public class Main extends RecursiveTask<Integer> {    private static final int THRESHOLD = 100;    private int start;    private int end;    public Main(int start, int end){        this.start = start;        this.end = end;    }    protected Integer compute(){        int sum = 0;        boolean canComputer = (end - start) <= THRESHOLD;        //如果任务规模小于 阈值 THRESHOLD 就直接计算        if(canComputer){            for(int i = start; i <= end; i++){                sum += i;            }        }else{            //如果任务大于阈值就裂变为两个子任务            int middle = (start + end) / 2;            Main leftTask = new Main(start, middle);            Main rightTask = new Main(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 pool = new ForkJoinPool();        Main task = new Main(1, 10000);        Future<Integer> result = pool.submit(task);        try{            System.out.println("result is:" + result.get());        }catch(InterruptedException e){        }catch(ExecutionException e){        }finally{            pool.shutdown();        }    }}

7.参考文献

论文《A Java Fork/Join Framework》–by Doug Lea]
https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
http://www.slideshare.net/hongjiang/java7-fork-join-framework-and-closures
http://www.importnew.com/2279.html
http://www.infoq.com/cn/articles/fork-join-introduction

原创粉丝点击