前两篇文章已经对Fork Join的设计和JDK中源码的简要分析。这篇文章,我们来简单地看看我们在开发中怎么对JDK提供的工具类进行应用,以提高我们的需求处理效率。

  Fork Join这东西确实用好了能给我们的任务处理提高效率,也为开发带来方便。但Fork Join不是那么容易用好的,我们先来看几个例子(反例)。

0. 反例错误分析

  我们先来看看这篇文章中提供的例子:http://www.iteye.com/topic/643724 (因为是反例,就不提供超链接了,只以普通文本给出URL)

  这篇文章是我学习和整理Fork Join时搜索到的一篇文章,其实总的来说这篇文章前面分析得还是比较好的,只是给出的第一个例子(有返回结果的RecursiveTask应用的例子) 没有正确地对Fork Join进行应用。为了方便分析,还是贴下这个例子中具体的的代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Calculator extends RecursiveTask {
 
    private static final int THRESHOLD = 100;
    private int start;
    private int end;
 
    public Calculator(int start, int end) {
        this.start = start;
        this.end = end;
    }
 
    @Override
    protected Integer compute() {
        int sum = 0;
        if((start - end) < THRESHOLD){
            for(int i = start; i< end;i++){
                sum += i;
            }
        }else{
            int middle = (start + end) /2;
            Calculator left = new Calculator(start, middle);
            Calculator right = new Calculator(middle + 1, end);
            left.fork();
            right.fork();
 
            sum = left.join() + right.join();
        }
        return sum;
    }
 
}

  我们看到其中一段已经高亮的代码,显示对两个子任务进行fork()调用,即分别提交给当前线程的任务队列,依次加到末尾。紧接着,又按照调用fork()的顺序执行两个子任务对象的join()方法。

  其实,这样就有一个问题,在每次迭代中,第一个子任务会被放到线程队列的倒数第二个位置,第二个子任务是最后一个位置。当执行join()调用的时 候,由于第一个子任务不在队列尾而不能通过执行ForkJoinWorkerThread的unpushTask()方法取出任务并执行,线程最终只能挂 起阻塞,等待通知。而Fork Join本来的做法是想通过子任务的合理划分,避免过多的阻塞情况出现。这样,这个例子中的操作就违背了Fork Join的初衷,每次子任务的迭代,线程都会因为第一个子任务的join()而阻塞,加大了代码运行的成本,提高了资源开销,不利于提高程序性能。

  除此之外,这段程序还是不能进入Fork Join的过程,因为还有一个低级错误。看下第15、16行代码的条件,就清楚了。按照逻辑,start必然是比end小的。这将导致所有任务都将以循环累加的方式完成,而不会执行fork()和join()。

  由此可见,Fork Join的使用还是要注意对其本身的理解和对开发过程中细节的把握的。我们看下JDK中RecursiveAction和RecursiveTask这两个类。

1. RecursiveAction分析及应用实例

  这两个类都是继承了ForkJoinTask,本身给出的实现逻辑并不多不复杂,在JDK的类文件中,它的注释比源码还要多。我们可以看下它的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class RecursiveAction extends ForkJoinTask<Void> {
    private static final long serialVersionUID = 5232453952276485070L;
 
    protected abstract void compute();
 
    public final Void getRawResult() { return null; }
 
    protected final void setRawResult(Void mustBeNull) { }
 
    protected final boolean exec() {
        compute();
        return true;
    }
}

  我们看到其中两个方法是关于处理空返回值的方法。而exec方法则是调用了compute(),这个compute就是我们使用Fork Join时需要自己实现的逻辑。

  我们可以看下API中给出的一个最简单最具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class IncrementTask extends RecursiveAction {
   final long[] array; final int lo; final int hi;
   IncrementTask(long[] array, int lo, int hi) {
     this.array = array; this.lo = lo; this.hi = hi;
   }
   protected void compute() {
     if (hi - lo < THRESHOLD) {
       for (int i = lo; i < hi; ++i)
         array[i]++;
     }
     else {
       int mid = (lo + hi) >>> 1;
       invokeAll(new IncrementTask(array, lo, mid),
                 new IncrementTask(array, mid, hi));
     }
   }
 }

  大致的逻辑就是,对给定一个特定数组的某段,进行逐个加1的操作。我们看到else中的代码块,显示取一个lo和hi的中间值,此后分割成两个子任务,并进行invokeAll()调用。我们来看下继承自FutureTask的invokeAll()方法实现。很简单:

1
2
3
4
5
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
    t2.fork();
    t1.invoke();
    t2.join();
}

  对于参数中的两个子任务,对第二个子任务进行fork(),即放入线程对应队列的结尾,然后执行第一个子任务,再调用第二个子任务的join(),实际上就是跳转到第二个子任务,进行执行(当然如果不能执行,就需要阻塞等待了)。

  其实invokeAll()是个重载方法,同名的还有另外两个,基本逻辑都是一样的,我们拿出一个通用一点的来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void invokeAll(ForkJoinTask<?>... tasks) {
    Throwable ex = null;
    int last = tasks.length - 1;
    for (int i = last; i >= 0; --i) {
        ForkJoinTask<?> t = tasks[i];
        if (t == null) {
            if (ex == null)
                ex = new NullPointerException();
        }
        else if (i != 0)
            t.fork();
        else if (t.doInvoke() < NORMAL && ex == null)
            ex = t.getException();
    }
    for (int i = 1; i <= last; ++i) {
        ForkJoinTask<?> t = tasks[i];
        if (t != null) {
            if (ex != null)
                t.cancel(false);
            else if (t.doJoin() < NORMAL && ex == null)
                ex = t.getException();
        }
    }
    if (ex != null)
        UNSAFE.throwException(ex);
}

  我们发现第一个子任务(i==0的情况)没有进行fork,而是直接执行,其余的统统先调用fork()放入任务队列,之后再逐一join()。其 实我们注意到一个要点就是第一个任务不要fork()再join(),也就是上面中例子的错误所在,这样会造成阻塞,而不能充分利用Fork Join的特点,也就不能保证任务执行的性能。

2. RecursiveTask简要说明

  其实说完了RecursiveAction,RecursiveTask可以用“同理”来解释。实现代码也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    private static final long serialVersionUID = 5232453952276485270L;
 
    V result;
 
    protected abstract V compute();
 
    public final V getRawResult() {
        return result;
    }
 
    protected final void setRawResult(V value) {
        result = value;
    }
 
    protected final boolean exec() {
        result = compute();
        return true;
    }
 
}

  我们看到唯一不同的是返回结果的处理,其余都可以和RecursiveAction一样使用。

3. Fork Join应用小结

  Fork Join是为我们提供了一个非常好的“分而治之”思想的实现平台,并且在一定程度上实现了“变串行并发为并行”。但Fork Join不是万能的页不完全是通用的,对于可很好分解成子任务的场景,我们可以对其进行应用,更多时候要考虑需求和应用场景,并且注意其使用要点才行。

Java7中的ForkJoin并发框架初探(下)—— ForkJoin的应用的更多相关文章

  1. [转]Java7中的ForkJoin并发框架初探(下)—— ForkJoin的应用

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp86   前两篇文章已经对Fork Join的设计和JDK中源码的简要分析 ...

  2. Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析

    原文发表于 2013 年 8 月 28 日 由 三石 根据前文描述的Doug Lea的理论基础,在JDK1.7中已经给出了Fork Join的实现.在Java SE 7的API中,多了ForkJoin ...

  3. [转]Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp85   根据前文描述的Doug Lea的理论基础,在JDK1.7中已经给 ...

  4. [转]Java7中的ForkJoin并发框架初探(上)——需求背景和设计原理

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp83 这篇我们来简要了解一下JavaSE7中提供的一个新特性 -- For ...

  5. Java7中的ForkJoin并发框架初探(上)——需求背景和设计原理

    原文:发表于 2013 年 8 月 26 日 由 三石 0. 处理器发展和需求背景 回想一下并发开发的初衷,其实可以说是有两点,或者说可以从两个方面看. 对于单核的处理器来说,在进行IO操作等比较费时 ...

  6. Python3中tornado高并发框架

    1.单线程tornado.web:基础web框架模块tornado.ioloop:核心IO循环模块,高效的基础.封装了:1.asyncio 协程,异步处理2. epoll模型:水平触发(状态改变就询问 ...

  7. 来,带你鸟瞰 Java 中4款常用的并发框架!

    1. 为什么要写这篇文章 几年前 NoSQL 开始流行的时候,像其他团队一样,我们的团队也热衷于令人兴奋的新东西,并且计划替换一个应用程序的数据库. 但是,当深入实现细节时,我们想起了一位智者曾经说过 ...

  8. 使用 ACE 库框架在 UNIX 中开发高性能并发应用

    使用 ACE 库框架在 UNIX 中开发高性能并发应用来源:developerWorks 中国 作者:Arpan Sen ACE 开放源码工具包可以帮助开发人员创建健壮的可移植多线程应用程序.本文讨论 ...

  9. JAVA并行框架学习之ForkJoin

    当硬件处理能力不能按照摩尔定律垂直发展的时候,选择了水平发展,多核处理器已经广泛应用.未来随着技术的进一步发展,可能出现成百上千个处理核心,但现有的程序运行在多核心处理器上并不能得到较大性能的提升,主 ...

随机推荐

  1. dotweb——go语言的一个微型web框架(一)

    dotweb是16年正式托管到github的一个开源项目,go语言的web框架目前也有很多,出名的有bee和echo.它们都是很优秀的框架,但是我们喜欢更轻.更小的东西,经历一些之后我们更青睐微服务这 ...

  2. raise RuntimeError("autoconf error") RuntimeError: autoconf error

    pip 安装模块时遇到下错误,没有粘贴全,差不多都是这样.这个情况是 pip 安装模块 需要 gcc 及 python-devle 支持, ubuntu 是 python-dev ,使用Yum 安装即 ...

  3. Spring+SpringMVC+MyBatis+easyUI整合优化篇(二)Log4j讲解与整合

    日常啰嗦 上一篇文章主要讲述了一下syso和Log间的一些区别与比较,重点是在项目的日志功能上,因此,承接前文<Spring+SpringMVC+MyBatis+easyUI整合优化篇(一)Sy ...

  4. python+request+robot framework接口自动化测试

    python+requests实现接口的请求前篇已经介绍,还有不懂或者疑问的可以访问 python+request接口自动化框架 目前我们需要考虑的是如何实现关键字驱动实现接口自动化输出,通过关键字的 ...

  5. UNIX标准

    背景 人们在UNIX编程环境和C 程序设计语言的标准化方面已经做了很多工作.虽然UNIX应用 程序在不同的UNIX操作系统版本之间进行移植相当容易,但是2 0 世 纪 80年代UNIX版本种类的剧增以 ...

  6. ABP框架实战 1.基础信息维护

    在之前的一个开发项目中,因为公司战略发展,引用了这个ABP开源框架作为新项目的基础版本,由于客户的要求需要迁移旧系统数据,以及其他的一些原因,数据库采用了Oracle数据库管理.所以引用了Dapper ...

  7. 老李分享:大数据框架Hadoop和Spark的异同 2

    Spark数据处理速度秒杀MapReduce Spark因为其处理数据的方式不一样,会比MapReduce快上很多.MapReduce是分步对数据进行处理的: ”从集群中读取数据,进行一次处理,将结果 ...

  8. Struts2基础学习(七)—值栈和OGNL

    目录: 一.值栈 二.OGNL表达式 一.值栈(ValueStack) 1.定义      ValueStack贯穿整个Acton的生命周期,每个Action类的对象实例都拥有一个ValueStack ...

  9. 学习HTML5的第二周

    ---恢复内容开始--- 这是我学习H5的第二周,在本周,我独立完成了一个网站的首页和一个二级页,虽然在做网页的时候我遇到了许多问题,但我自己想办法解决了其中的大部分,只留下了一小部分没有头绪的问题等 ...

  10. __init__.py

    python中的Module是比较重要的概念.常见的情况是,事先写好一个.py文 件,在另一个文件中需要import时,将事先写好的.py文件拷贝 到当前目录,或者是在sys.path中增加事先写好的 ...