Java并发——Fork/Join框架

转自:http://brokendreams.iteye.com/blog/2257941

  • ForkJoin框架是什么?
       ForkJoin框架是jdk1.7提供的一个并行计算框架。

  • ForkJoin框架能干什么?
       首先ForkJoin框架是针对一些符合 ForkJoin模型的任务而设计的,那什么是ForkJoin模型呢?看个图先:


          注:图片来之 https://en.wikipedia.org/wiki/Fork%E2%80%93join_model
 
       ForkJoin模型是指一些任务在执行过程中会分裂出一些子任务(或子过程),而且这些子任务是可以并行执行的(相互没有制约),子任务们执行完毕后合并到主任务中,主任务继续执行。这种模型本身也体现了分而治之的思想。
 
       能体现ForkJoin模型的典型栗子就是归并排序,归并排序这个老黄历就不解释了,还是看个图吧: 


            注:图片来之 http://www.cnblogs.com/horizonice/p/4102553.html
 
       可见在归并过程中,会不断分裂出许多子任务,而同一层级子合并任务之间不会相互制约,可以并行执行。
       ForkJoin框架就是针对这种模型,提供的一套并行计算框架,可以很好的利用多核的能力来挖掘任务的并行性。目的是缩短任务的总体执行时间,同时也能充分的利用计算机资源。
        注:ForkJoin是一个单机框架,类似的分布式的框架有Hadoop这类的,它们的计算模型是MapReduce,体现了和ForkJoin一样的思想-分而治之。
 
  • 能不能给个ForkJoin框架的使用示例?
       为了更关注于框架本身,这里给一个最简单的示例,计算整数1到n的累加结果。(假设我们不知道高斯求和公式...)
       首先需要定义一个任务:
Java代码   收藏代码
  1. public class SumTask extends RecursiveTask{  
  2.     private static final int THRESHOLD = 10;  
  3.       
  4.     private long start;  
  5.     private long end;  
  6.       
  7.     public SumTask(long n) {  
  8.         this(1,n);  
  9.     }  
  10.       
  11.     private SumTask(long start, long end) {  
  12.         this.start = start;  
  13.         this.end = end;  
  14.     }  
  15.     @Override  
  16.     protected Long compute() {  
  17.         long sum = 0;  
  18.         if((end - start) <= THRESHOLD){  
  19.             for(long l = start; l <= end; l++){  
  20.                 sum += l;  
  21.             }  
  22.         }else{  
  23.             long mid = (start + end) >>> 1;  
  24.             SumTask left = new SumTask(start, mid);  
  25.             SumTask right = new SumTask(mid + 1, end);  
  26.             left.fork();  
  27.             right.fork();  
  28.             sum = left.join() + right.join();  
  29.         }  
  30.         return sum;  
  31.     }  
  32.     private static final long serialVersionUID = 1L;  
  33. }  
       可见我们定义的任务首先要继承RecursiveTask类。 简单说明一下RecursiveTask,它表示一种有返回值的ForkJoin任务,对应的无返回值(比如给数组排序这种任务)的版本是RecursiveAction。
       代码中定义了一个阀值10,也就是说我们会把1-n拆分成1-10、10-20...这样的长度小于10的分段(代码中fork出子任务),然后分别计算这些分段和,然后再不断汇总(通过join来获取子任务的结果),得到最终的结果。
 
       任务定义好了,下面我们来执行这个任务: 
Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.     ForkJoinPool forkJoinPool = new ForkJoinPool();  
  3.     //计算1-100的和。  
  4.     SumTask sumTask = new SumTask(100);  
  5.     ForkJoinTask forkJoinTask = forkJoinPool.submit(sumTask);  
  6.     try {  
  7.         long result = forkJoinTask.get();  
  8.         System.out.println("result="+result);  
  9.     } catch (InterruptedException e) {  
  10.         e.printStackTrace();  
  11.     } catch (ExecutionException e) {  
  12.         e.printStackTrace();  
  13.     }  
  14. }  
       输出结果: 
Java代码   收藏代码
  1. result=5050  
       通过执行代码可见,我们会将任务提交到ForkJoinPool中,等待任务被执行完毕后,获取执行结果。是不是发现这种使用方式和JDK线程池的使用方式有些类似,没错,ForkJoinPool也是ExecutorService的一种实现并且ForkJoinTask也是Future的一种实现。
        注:JDK1.8中的Arrays提供了并行排序(parallelSort),就是利用ForkJoin框架来做的。
 
  • ForkJoin框架内部组件及工作原理
       ForkJoin框架包括下面这些类:
               ForkJoinPool
              ForkJoinWorkerThread
              ForkJoinTask
              RecursiveTask
              RecursiveAction
 
        ForkJoinPool充当ForkJoin工作线程池的角色,管理和维护ForkJoin的工作线程,也管理一部分任务。
       ForkJoinWorkerThread就是执行ForkJoin任务的工作线程。
       ForkJoinTask是ForkJoin任务,它定义ForkJoin任务的一些基础行为。
       RecursiveTask和RecursiveAction都继承自ForkJoinTask,分别表示有返回值和无返回值的任务。
 
      ForkJoin框架的主要逻辑功能都实现在ForkJoinPool、ForkJoinWorkerThread和ForkJoinTask中,RecursiveTask和RecursiveAction中只是简单的做了返回值的逻辑,然后开放了compute方法交由子类去实现具体的计算逻辑。
 
       ForkJoin框架内部采用了work-stealing(工作窃取)模式:每个工作线程都有自己的任务队列(一般是双端队列),工作线程首先会处理自己任务队列里面的任务,如果自己任务队列中没有任务了,会从其他工作线程的任务队列中窃取任务来执行。
        注:关于工作窃取的理论可以看下《多处理器编程的艺术》中的第16章相关内容,另外本书包含了JUC包实现的很多理论基础,虽然比较枯燥,但很值得一读。
 
  • ForkJoin框架内部重要类概览
       下面对ForkJoinPool、ForkJoinWorkerThread和ForkJoinTask这三个类做下概览,后续的源码分析也主要针对这三个类进行。
 
        首先看下ForkJoinPool,这个类是整个ForkJoin框架的主体,简单看下内部一些重要的域: 
Java代码   收藏代码
  1. /** 
  2.  * ForkJoinPool的总控制信息,包含在一个long里面: 
  3.  * AC: 表示当前活动的工作线程的数量减去并行度得到的数值。(16 bits) 
  4.  * TC: 表示全部工作线程的数量减去并行度得到的数值。(16bits) 
  5.  * ST: 表示当前ForkJoinPool是否正在关闭。(1 bit) 
  6.  * EC: 表示Treiber stack顶端的等待工作线程的等待次数。(15 bits) 
  7.  * ID: Treiber stack顶端的等待工作线程的下标取反(16 bits) 
  8.  * 
  9.  * 1111111111111111 1111111111111111  1  111111111111111 1111111111111111 
  10.  * AC               TC                ST EC              ID  
  11.  * 
  12.  * 如果AC为负数,说明没有足够的活动工作线程。 
  13.  * 如果TC为负数,说明工作线程数量没达到最大工作线程数量。 
  14.  * 如果ID为负数,说明至少有一个等待的工作线程。 
  15.  * 如果(int)ctl为负数,说明ForkJoinPool正在关闭。 
  16.  */  
  17. volatile long ctl;  
  18. // bit positions/shifts for fields  
  19. private static final int  AC_SHIFT   = 48;  
  20. private static final int  TC_SHIFT   = 32;  
  21. private static final int  ST_SHIFT   = 31;  
  22. private static final int  EC_SHIFT   = 16;  
  23. // bounds  
  24. private static final int  MAX_ID     = 0x7fff;  // max poolIndex  
  25. private static final int  SMASK      = 0xffff;  // mask short bits  
  26. private static final int  SHORT_SIGN = 1 << 15;  
  27. private static final int  INT_SIGN   = 1 << 31;  
  28. // masks  
  29. private static final long STOP_BIT   = 0x0001L << ST_SHIFT;  
  30. private static final long AC_MASK    = ((long)SMASK) << AC_SHIFT;  
  31. private static final long TC_MASK    = ((long)SMASK) << TC_SHIFT;  
  32. // units for incrementing and decrementing  
  33. private static final long TC_UNIT    = 1L << TC_SHIFT;  
  34. private static final long AC_UNIT    = 1L << AC_SHIFT;  
  35. // masks and units for dealing with u = (int)(ctl >>> 32)  
  36. private static final int  UAC_SHIFT  = AC_SHIFT - 32;  
  37. private static final int  UTC_SHIFT  = TC_SHIFT - 32;  
  38. private static final int  UAC_MASK   = SMASK << UAC_SHIFT;  
  39. private static final int  UTC_MASK   = SMASK << UTC_SHIFT;  
  40. private static final int  UAC_UNIT   = 1 << UAC_SHIFT;  
  41. private static final int  UTC_UNIT   = 1 << UTC_SHIFT;  
  42. // masks and units for dealing with e = (int)ctl  
  43. private static final int  E_MASK     = 0x7fffffff// no STOP_BIT  
  44. private static final int  EC_UNIT    = 1 << EC_SHIFT;  
       ctl是ForkJoinPool中最重要的,也是设计最精密的域,它是整个ForkJoinPool的总控信息。所有信息包含在一个long(64bit)中,这些信息包括:当前活动的工作线程数量、当前总的工作线程数量、ForkJoinPool的关闭标志、在Treiber stack(由全部等待工作线程组成的一个链)顶端等待的工作线程的等待次数、Treiber stack(由全部等待工作线程组成的一个链)顶端等待的工作线程的ID信息(工作线程的下标取反)。
       ctl还有一个相对不重要的作用就是,某些非volatile域会依赖ctl来保证可见性。 
 
Java代码   收藏代码
  1. ForkJoinWorkerThread[] workers;  
       workers是ForkJoinPool中保存工作线程的数组,它的更新会由一个锁(scanGuard)来保护。 
 
Java代码   收藏代码
  1. volatile int scanGuard;  
       scanGuard是另外一个比较重要的域,它有两个作用:1.作为更新工作线程数组是使用的(顺序)锁;2.作为扫描工作线程数组时使用的边界值来避免一些没用的扫描(当数组过大时)。 
 
Java代码   收藏代码
  1. private static final int INITIAL_QUEUE_CAPACITY = 8;  
  2.   
  3. private static final int MAXIMUM_QUEUE_CAPACITY = 1 << 24// 16M  
  4.   
  5. private ForkJoinTask[] submissionQueue;  
  6.   
  7. private final ReentrantLock submissionLock;  
       ForkJoinPool中也有一个队列-submissionQueue,这个队列里存放的是有外部(非ForkJoin工作线程)提交到ForkJoinPool中的任务。
Java代码   收藏代码
  1. volatile int queueBase;  
  2.   
  3. int queueTop;  
       这两个域分别表示submissionQueue的顶部和底部。  
 
Java代码   收藏代码
  1. final boolean locallyFifo;  
       locallyFifo域也比较重要,它有ForkJoinPool的构造方法的参数asyncMode来指定。如果locallyFifo为true,表示内部将才用FIFO的方式来调度任务队列中的任务,而且这些任务可以分裂(fork),但最好不要合并(join),这种模式很适合来处理事件形式(event-style)的异步任务。默认locallyFifo为false。
 
       ForkJoinPool中一些比较重要的域就简单介绍到这里,后续的源码解析中还会详细分析。
 
        再看下ForkJoinWorkerThread,这个类是ForkJoin框架中负责执行具体任务的工作线程,简单看下内部一些重要的域: 
Java代码   收藏代码
  1. private static final int INITIAL_QUEUE_CAPACITY = 1 << 13;  
  2.   
  3. private static final int MAXIMUM_QUEUE_CAPACITY = 1 << 24// 16M  
  4.   
  5. ForkJoinTask[] queue;  
  6.   
  7. int queueTop;  
  8.   
  9. volatile int queueBase;  
       queue就是ForkJoinWorkerThread中的任务队列,当从其他工作线程中窃取任务时,就是从这个队列中进行窃取。 
 
Java代码   收藏代码
  1. final int poolIndex;  
       工作线程在ForkJoinPool中工作线程数组中的下标。
 
Java代码   收藏代码
  1. int stealHint;  
       stealHint保存了最近的窃取者(来窃取任务的工作线程)的下标(poolIndex)。注意这个值不准确,因为可能同时有很多窃取者来窃取任务,这个值只能记录其中之一。  
 
Java代码   收藏代码
  1. int nextWait;  
       nextWait算是比较难理解的一个域。首先所有的等待工作线程组成了一个隐式的单链(代码中也叫Treiber stack,由于行为类似于栈),链顶端的等待工作线程的信息保存在Pool的ctl中,新来的等待工作线程会将ctl中之前的等待工作线程信息保存到nextWait上,然后将自己的信息设置到ctl上。 
 
Java代码   收藏代码
  1. final boolean locallyFifo;  
       这个和Pool中的locallyFifo一致。 
 
Java代码   收藏代码
  1. ForkJoinTask currentSteal;  
       当前工作线程最新窃取的任务。注意可以从其他工作线程的任务队列或者从Pool中的提交任务队列(submissionQueue)中窃取任务。 
 
Java代码   收藏代码
  1. ForkJoinTask currentJoin;  
       当前工作线程正在合并的任务。
 
        最后看下ForkJoinTask,这个类表示ForkJoin框架中执行的任务,简单看下内部域: 
Java代码   收藏代码
  1. /** The run status of this task */  
  2. volatile int status; // accessed directly by pool and workers  
  3. private static final int NORMAL      = -1;  
  4. private static final int CANCELLED   = -2;  
  5. private static final int EXCEPTIONAL = -3;  
  6. private static final int SIGNAL      =  1;  
       ForkJoinTask中只有一个表示运行状态的域。初始为0;1表示在等待被唤醒;负数都表示执行完毕,-1表示正常完成、-2表示被取消、-3表示异常结束。
 
  • 最后
       本篇的内容到此结束,由于本系列侧重于源码分析,所以这里只是简单的介绍了一下ForkJoin框架。
       后续的文章会进行源码细节的分析,ForkJoin框架为了性能的提升,源码中充斥着耦合、内联、位操作、细节优化等情况,再加上框架本身比较精密的设计,所以源码读起来会有一点点蛋疼,所以请务必了解下本篇介绍的ForkJoinPool大体的工作模式和组件类中一些重要域的作用。
       后续分析文章不会按照类为单位来分析(由于太耦合,这样分析会屎),而是按照一些运行过程来分析,请先了解下大体的执行过程。 

你可能感兴趣的:(concurrent)