Fork-Join 原理深入分析(二)
本文是将 Fork-Join 复杂且较为庞大的框架分成5个小点来分析 Fork-Join 框架的实现原理,一个个点地理解透 Fork-Join 的核心原理。
1. Frok-Join 框架的核心类的结构分析
Fork-Join 框架有三个核心类:ForkJoinPool,ForkJoinWorkerThread,ForkJoinTask。下面将分析这三个类的数据结构,初步了解三个类的核心成员。
ForkJoinPool
//继承了 AbstractExecutorService 类
public class ForkJoinPool extends AbstractExecutorService{
    //任务队列数组,存储了所有任务队列,包括 内部队列 和 外部队列
    volatile WorkQueue[] workQueues;     // main registry
    //一个静态常量,ForkJoinPool 提供的内部公用的线程池
    static final ForkJoinPool common;
    //默认的线程工厂类
  public static final ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory;
}
ForkJoinWorkerThread
//继承了 Thread 类
public class ForkJoinWorkerThread extends Thread {
  //线程工作的线程池,即此线程所属的线程池
  final ForkJoinPool pool;
  // 线程的内部队列
  final ForkJoinPool.WorkQueue workQueue;
//.....
}
2. ForkJoinPool 中线程的创建
2.1 默认的线程工厂类
ForkJoinPool 中的线程是由默认的线程工厂类 defaultForkJoinWorkerThreadFactory 创建的
//默认的工厂类
  public static final ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory;
defaultForkJoinWorkerThreadFactory =
            new DefaultForkJoinWorkerThreadFactory();
defaultForkJoinWorkerThreadFactory 创建线程的方法 newThread(),其实就是传入当前的线程池,直接创建。
 /**
     * Default ForkJoinWorkerThreadFactory implementation; creates a
     * new ForkJoinWorkerThread.
     */
    static final class DefaultForkJoinWorkerThreadFactory
        implements ForkJoinWorkerThreadFactory {
        public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
            return new ForkJoinWorkerThread(pool);
        }
    }
2.2 ForkJoinWorkerThread 的构造方法
 protected ForkJoinWorkerThread(ForkJoinPool pool) {
        // Use a placeholder until a useful name can be set in registerWorker
        super("aForkJoinWorkerThread");
        //线程工作的线程池,即创建这个线程的线程池
        this.pool = pool;
        //注册线程到线程池中,并返回此线程的内部任务队列
        this.workQueue = pool.registerWorker(this);
    }
创建一个工作线程,最后一步还要注册到其所属的线程池中,看下面源码,注册的过程可以分为两步:
- 创建一个新的任务队列
- 为此任务队列分配一个线程池的索引,将任务队列存储在线程数组 workQueues 的此索引位置,并返回这个任务队列,作为线程的内部任务队列。线程注册成功。
  final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        UncaughtExceptionHandler handler;
        wt.setDaemon(true);               // configure thread
        if ((handler = ueh) != null)
            wt.setUncaughtExceptionHandler(handler);
        //创建一个任务队列
        WorkQueue w = new WorkQueue(this, wt);
        int i = 0;                             //分配一个线程池的索引
        int mode = config & MODE_MASK;
        int rs = lockRunState();
        try {
            WorkQueue[] ws; int n;                    // skip if no array
            if ((ws = workQueues) != null && (n = ws.length) > 0) {
                int s = indexSeed += SEED_INCREMENT; // unlikely to collide
                int m = n - 1;
                //计算 索引
                i = ((s << 1) | 1) & m;               // odd-numbered indices
               if (ws[i] != null) {                   //如果索引冲突
                    int probes = 0;                   // step by approx half n
                    int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                    while (ws[i = (i + step) & m] != null) {
                        if (++probes >= n) {
                            //扩容:以原来的数组的长度的两倍来创建一个新的数组,再复制旧数组的内衣
                            workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                            m = n - 1;
                            probes = 0;
                        }
                    }
                }
                w.hint = s;                           // use as random seed
                w.config = i | mode;
                w.scanState = i;                      // publication fence
                //刚创建的任务队列加入到线程池的 任务队列数组中
                ws[i] = w;
            }
        } finally {
            unlockRunState(rs, rs & ~RSLOCK);
        }
        wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
        return w;
    }
  对应注册线程,ForkJoinPool 也提供了一个取消线程注册的方法 deregisterWorker(),在线程被销毁的时候调用,此处就不说了。
3. ForkJoinTask的fork()、join()方法
  在上一篇文章中,我们在实现 分治编程时,主要就是调用 ForkJoinTask 的 fork() 和 join() 方法。fork() 方法用于提交子任务,而 join() 方法则用于等待子任务的完成。而这个过程中,将涉及到 “工作窃取算法”。
3.1 fork( ) 方法提交任务
先来看一下 fork() 方法的源码
public final ForkJoinTask<V> fork() {
        Thread t;
        //判断是否是一个 工作线程
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            //加入到内部队列中
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else//由 common 线程池来执行任务
            ForkJoinPool.common.externalPush(this);
        return this;
    }
  源码中,fork()方法先判断当前线程(调用fork()来提交任务的线程)是不是一个 ForkJoinWorkerThread 的工作线程,如果是,则将任务加入到内部队列中,否则,由 ForkJoinPool 提供的内部公用的线程池 common 线程池 来执行这个任务。
   //ForkJoinPool 提供的内部公用的线程池
    static final ForkJoinPool common;
  顺便说一下,根据上面的说法,意味着我们可以在普通线程池中直接调用 fork() 方法来提交任务到一个默认提供的线程池中。这将非常方便。假如,你要在程序中处理大任务,需要分治编程,但你仅仅只处理一次,以后就不会用到,而且任务不算太大,不需要设置特定的参数,那么你肯定不想为此创建一个线程池,这时默认的提供的线程池将会很有用。
下面是我基于上一篇文章例子改造的,CountTask 类在我上一篇文章中找到
public class Test_34 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建一个计算任务,计算 由1加到12
		CountTask countTask2 = new CountTask(1, 12);
        //直接在main线程中调用 fork 来提交任务,
		countTask2.fork();
        //没有创建线程池,使用commonPool线程池
		System.out.println(countTask2.get());
	}
}
运行结果:
任务过大,切割的任务: 1加到 12的和 执行此任务的线程:ForkJoinPool.commonPool-worker-1
任务过大,切割的任务: 1加到 6的和 执行此任务的线程:ForkJoinPool.commonPool-worker-2
任务过大,切割的任务: 7加到 12的和 执行此任务的线程:ForkJoinPool.commonPool-worker-3
执行计算任务,计算 1到 3的和 ,结果是:6 执行此任务的线程:ForkJoinPool.commonPool-worker-2
执行计算任务,计算 4到 6的和 ,结果是:15 执行此任务的线程:ForkJoinPool.commonPool-worker-1
执行计算任务,计算 7到 9的和 ,结果是:24 执行此任务的线程:ForkJoinPool.commonPool-worker-3
执行计算任务,计算 10到 12的和 ,结果是:33 执行此任务的线程:ForkJoinPool.commonPool-worker-1
78
注意执行任务的线程名称:commonPool表示执行任务的线程是公用的ForkJoinPooL线程池中的线程,上面的例子中,并没有创建一个新的ForKJoinPool线程池
3.2 join( ) 等待任务的完成
 public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();//直接返回结果
    }
  重点在 dojoin() 方法,下面追踪下去
 private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
        return
            //如果完成,直接返回s
            (s = status) < 0 ? s :
            //没有完成,判断是不是池中的 ForkJoinWorkerThread 工作线程
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            //如果是池中线程,执行这里
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
            wt.pool.awaitJoin(w, this, 0L) :
            //如果不是池中的线程池,则执行这里
            externalAwaitDone();
    }
  仔细看上面的注释。当 dojoin( )方法发现任务没有完成且当前线程是池中线程时,执行了 tryUnpush( )方法。tryUnpush()方法尝试去执行此任务:如果要join的任务正好在当前任务队列的顶端,那么pop出这个任务,然后调用 doExec() 让当前线程去执行这个任务。
 final boolean tryUnpush(ForkJoinTask<?> t) {
            ForkJoinTask<?>[] a; int s;
            if ((a = array) != null && (s = top) != base &&
                U.compareAndSwapObject
                (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
                U.putOrderedInt(this, QTOP, s);
                return true;
            }
            return false;
        }
 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;
    }
  如果任务不是处于队列的顶端,那么就会执行 awaitJoin( ) 方法。
 /**
     * Helps and/or blocks until the given task is done or timeout.
     *
     * @param w caller
     * @param task the task
     * @param deadline for timed waits, if nonzero
     * @return task status on exit
     */
    final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
        int s = 0;
        if (task != null && w != null) {
            ForkJoinTask<?> prevJoin = w.currentJoin;
            U.putOrderedObject(w, QCURRENTJOIN, task);
            CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
                (CountedCompleter<?>)task : null;
            for (;;) {
                if ((s = task.status) < 0)//如果任务完成了,跳出死循环
                    break;
                if (cc != null)//当前任务是CountedCompleter类型,则尝试从任务队列中获取当前任务的派生子任务来执行;
                    helpComplete(w, cc, 0);
                else if (w.base == w.top || w.tryRemoveAndExec(task))//如果当前线程的内部队列为空,或者成功完成了任务,帮助某个线程完成任务。
                    helpStealer(w, task);
                if ((s = task.status) < 0)//任务完成,跳出死循环
                    break;
                long ms, ns;
                if (deadline == 0L)
                    ms = 0L;
                else if ((ns = deadline - System.nanoTime()) <= 0L)
                    break;
                else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                    ms = 1L;
                if (tryCompensate(w)) {
                    task.internalWait(ms);
                    U.getAndAddLong(this, CTL, AC_UNIT);
                }
            }
            U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
        }
        return s;
    }
重点说一下helpStealer。helpStealer的原则是你帮助我执行任务,我也帮你执行任务。
- 遍历奇数下标,如果发现队列对象currentSteal放置的刚好是自己要找的任务,则说明自己的任务被该队列A的owner线程偷来执行
- 如果队列A队列中有任务,则从队尾(base)取出执行;
- 如果发现队列A队列为空,则根据它正在join的任务,在拓扑找到相关的队列B去偷取任务执行。
 在执行的过程中要注意,我们应该完整的把任务完成
还有剩下的几个比较核心的部分源码就不再此处分析,提供两个比较棒的博文:(因为我还有一些疑惑没解决,以后再补充)
最后,有兴趣的还可以看一下Doug Lea 的写的Fork-Join 框架的文章
原文:A Java Fork/Join Framework
中文译文:Fork/Join 框架-设计与实现
参考文献:
Fork-Join 原理深入分析(二)的更多相关文章
- 谈谈fork/join实现原理
		害,又是一个炒冷饭的时间.fork/join是在jdk1.7中出现的一个并发工作包,其特点是可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出.从而达到多 ... 
- JUC组件扩展(二)-JAVA并行框架Fork/Join(一):简介和代码示例
		一.背景 虽然目前处理器核心数已经发展到很大数目,但是按任务并发处理并不能完全充分的利用处理器资源,因为一般的应用程序没有那么多的并发处理任务.基于这种现状,考虑把一个任务拆分成多个单元,每个单元分别 ... 
- ☕【Java技术指南】「并发编程专题」Fork/Join框架基本使用和原理探究(基础篇)
		前提概述 Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行. 我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一 ... 
- JUC组件扩展(二)-JAVA并行框架Fork/Join(四):监控Fork/Join池
		Fork/Join 框架是为了解决可以使用 divide 和 conquer 技术,使用 fork() 和 join() 操作把任务分成小块的问题而设计的.主要实现这个行为的是 ForkJoinPoo ... 
- JUC组件扩展(二)-JAVA并行框架Fork/Join(二):同步和异步
		在Fork/Join框架中,提交任务的时候,有同步和异步两种方式. invokeAll()的方法是同步的,也就是任务提交后,这个方法不会返回直到所有的任务都处理完了. fork方法是异步的.也就是你提 ... 
- 聊聊并发(八)——Fork/Join框架介绍
		作者 方腾飞 发布于 2013年12月23日 | 被首富的“一个亿”刷屏?不如定个小目标,先把握住QCon上海的优惠吧!2 讨论 分享到:微博微信FacebookTwitter有道云笔记邮件分享 ... 
- 转:聊聊并发(八)——Fork/Join框架介绍
		1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过 ... 
- Fork/Join框架详解
		Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.Fork/Join框架要完成两件事情: 1.任务分 ... 
- java Fork/Join框架
		应用程序并行计算遇到的问题 当硬件处理能力不能按摩尔定律垂直发展的时候,选择了水平发展.多核处理器已广泛应用,未来处理器的核心数将进一步发布,甚至达到上百上千的数量.而现在很多的应用程序在运行在多核心 ... 
随机推荐
- paho.mqtt.embedded-c MQTTPacket pub0sub1.c hacking
			/******************************************************************************* * paho.mqtt.embedde ... 
- caffe安装编译问题-ImportError: libopencv_core.so.3.4: cannot open shared object file: No such file or directory
			问题描述 >>> import caffe Traceback (most recent call last): File , in <module> File , in ... 
- [Data Structure] Linked List Implementation in Python
			class Empty(Exception): pass class Linklist: class _Node: # Nonpublic class for storing a linked nod ... 
- PHPEmailer使用简介(以qq邮箱为例)
			1.从网上下载PHPEmailer: 2.确保PHP环境支持sockets扩展,还要开启openssl,如下图: 3.配置QQ邮箱 1.开启SMTP服务 2.验证密保 3.获取授权码(这个就是IMAP ... 
- 1010. Radix (25) pat
			Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 110 be true? The an ... 
- SQLyog Enterprise常用快捷键
			1. SQL格式化 F12 格式化当前行所在的SQL Ctrl+F12 格式化选中的SQL Shift+F12 格式化所有SQL 2. 窗口操作 Ctrl+T 打开一个新的查询窗口 Alt+ ... 
- javascript的单例模式
			单例模式是javascript最基本,最有用的模式之一,它提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码通过单一的变量进行访问.我的理解是在这个作用域中,只有通过单一的变量来访问,不存 ... 
- CentOS升级Python2.6到Python2.7并安装pip
			原文:http://ruter.sundaystart.net/2015/12/03/Update-python/ 貌似CentOS 6.X系统默认安装的Python都是2.6版本的?平时使用以及很多 ... 
- stenciljs 学习十二 官方doc 路由使用的例子
			路由在单页面应用开发中是一个相对比较重要的位置 以下为官方网站的路由配置 <stencil-router scrollTopOffset={0}> <stencil-route-sw ... 
- vulcanjs 简单package 编写
			vulcanjs 功能是以包进行管理,包里面包含了运行依赖的组件以及对于路由的注册 参考项目 项目结构 ├── README.md ├── license.md ├── package-lock.js ... 
