【Java多线程】JUC之线程池(四)ForkJoin

文章目录

    • 补.常用多线程并发获取返回结果方法汇总
    • 什么是异步编程?
    • 一.什么是Fork/Join框架
      • 1.概述
      • 2.分治法
      • 3.工作窃取算法
    • 二.ForkJoinPool的工作模式
      • 1.对比ThreadPoolExecuto的工作模式
      • 2.Fork/Join工作方式
      • 3.简述Fork/Join框架的3个核心类
        • 3.1.ForkJoinPool
        • 3.2.ForkJoinWorkerThread
        • 3.3.ForkJoinTask
    • 三.ForkJoinPool
      • 1.概述
      • 2.继承体系
      • 3.ForkJoinPool成员变量
      • 4.ForkJoinPool状态说明(了解即可)
      • 5.ForkJoinPool主要方法
        • 5.1.构造函数
        • 5.2.提交任务
        • 5.3.执行任务
      • 6.工作线程-ForkJoinWorkerThread解析
          • 构造函数
      • 7.工作队列-WorkQueue
        • 主要方法说明
          • 1.入队方法
          • 2.出队方法
          • 3.执行任务
    • 四.ForkJoinTask
      • 1.概述
      • 2.继承体系
      • 3.如何使用
        • 3.1.使用RecursiveAction(无返回值)
        • 3.2.使用RecursiveTask(有返回值)
      • 4.主要方法
        • 4.1.任务状态
        • 4.2.fork
        • 4.3.join / quietlyJoin
        • 4.4.externalAwaitDone
        • 4.5.invoke / quietlyInvoke
        • 4.6.complete / quietlyComplete/ completeExceptionally
        • 4.7. recordExceptionalCompletion / clearExceptionalCompletion / getException
        • 4.8. invokeAll
        • 4.9. get
        • 4.10. tryUnfork / reinitialize
        • 4.11. FutureJoinTask任务的异常处理
        • 4.12. 获取任务状态、结果的方法
        • 4.12. 总结
    • 五.大总结

Java并发包提供的最后一个线程池实现,也是最复杂的一个线程池。针对这一部分的代码太复杂,本篇博客基于Java8分析,由于目前理解有限,只做简单介绍。

补.常用多线程并发获取返回结果方法汇总

描述 Future FutureTask CompletionService CompletableFuture
原理 Future接口 接口RunnableFuture的唯一实现类,RunnableFuture接口继承自Future+Runnable 内部通过阻塞队列+FutureTask接口 Java8实现了Future, CompletionStage两个接口
多任务并发执行 支持 支持 支持 支持
获取任务结果的顺序 按照提交顺序获取结果 未知 支持任务完成的先后顺序 支持任务完成的先后顺序
异常捕捉 自己捕捉 自己捕捉 自己捕捉 原生API支持,返回每个任务的异常
建议 CPU高速轮询,耗资源,或者阻塞,可以使用,但不推荐 功能不对口,并发任务这一块多套一层,不推荐使用 推荐使用Java8之前最好的方案 J API极端丰富,配合流式编程,推荐使用

什么是异步编程?

  • 异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度成功或者失败。在这种方式中,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。使用这种并行方式,可以极大的提高程序的性能。

一.什么是Fork/Join框架

1.概述

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

可以通过Fork和Join这两个单词来理解下Fork/Join框架

  • Fork就是把一个大任务 切分若干子任务并行的执行
  • Join就是合并这些子任务的执行结果,最后得到这个大任务的结果

比如计算·1+2+n+10000·,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总10个子任务的结果。

Fork/Join的运行流程图如下:

【Java多线程】JUC之线程池(四)ForkJoin_第1张图片
任务分割成任务1任务2任务1继续分割成任务1.1和任务1.2任务2继续分割成任务2.1和任务2.2,此时任务1.1、1.2、2.1、2.2都足够小了,不必继续分割,可以直接解决掉。任务1.1和1.2完成后得到任务1的结果,任务2.1和2.2完成后得到任务2的结果。最终将任务1和任务2的结果归并成任务的结果,本次ForkJoin结束。

2.分治法

分治就是“分而治之”的意思,就是把一个复杂的问题分成两个或更多的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解就是子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),以及汉诺塔等等。

分治算法用递归来实现,而 在每一层递归上都有三个步骤:

  1. 分解:将原问题分解为多个相互独立且与原问题形式相同的子问题
  2. 解决:若子问题足够小且不能够被分解了则直接解,否则递归地解各个子问题
  3. 合并:将各个子问题的解合并为原问题的解。

简单的问题,可以用二分法来完成。。对应到ForkJoinPool对问题的处理也如此。基本原理如下图:

就是我们之前在的时候经常用到的二分法查找(折半法,是一种在有序数组中查找特定元素的搜索算法)

【Java多线程】JUC之线程池(四)ForkJoin_第2张图片

上面一个大的任务,通过fork()方法不断拆解,直到能够计算为止,之后,再将这些结果用join()合并。这样逐次递归,就得到了我们想要的结果。这就是在ForkJoinPool中的分治法

分治法典型应用

  • 二分搜索
  • 大整数乘法
  • 归并排序
  • 快速排序
  • 汉诺塔

3.工作窃取算法

工作窃取(work-stealing)算法:指某个线程从其他队列窃取任务来执行。

  • 核心思想:自己的活干完了去看看别人有没有没干完的活,如果有就拿过来帮他干。
  • -大多数实现机制是:为每个工作线程分配一个双端队列(本地队列)用于存放需要执行的任务,当自己的任务队列没有数据的时候随机从其它工作线程的任务队列中获得一个任务继续执行。

工作窃取的运行流程图如下:
【Java多线程】JUC之线程池(四)ForkJoin_第3张图片

为什么需要使用工作窃取算法呢

  • 假如要执行一个比较大的任务,我们可以把这个大任务分割为若干互不依赖的子任务,为了减少线程间的竞争,再把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的工作线程来执行队列里的任务工作线程和队列一一对应比如A线程负责处理A队列里的任务。
  • 如果有的工作线程先把自己队列里的任务处理完了,而其他工作线程对应的队列里还有任务等待处理。处理完任务的工作线程就去其他工作线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,这样就避免空闲工作线程因为没有任务执行而产生无意义的等待。所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列被窃取任务线程 永远从双端队列的 "头部" 拿任务执行,而窃取任务的线程永远从双端队列的 "尾部" 拿任务执行

工作窃取算法的优点

  • 当某个工作线程的任务队列中没有可执行任务的时候,将从其他工作线程的任务队列中窃取任务来执行,以充分利用工作线程的计算能力,减少线程由于获取不到任务而造成的空闲浪费(即:进入等待状态)。
  • 充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。且该算法消耗了更多的系统资源,比如创建多个工作线程以及对应多个双端队列。

二.ForkJoinPool的工作模式

1.对比ThreadPoolExecuto的工作模式

ForkJoinPool采用了一个线程对应专属的一个工作队列,而非 ThreadPoolExecutor 的多个线程对应一个工作队列。即 线程与工作队列关系 由 多对一变为一对一

ThreadPoolExecutor 线程池模型大概如下
【Java多线程】JUC之线程池(四)ForkJoin_第4张图片
ForkJoinPool 的线程与工作队列对应模型

【Java多线程】JUC之线程池(四)ForkJoin_第5张图片

其实对于 ForkJoinPool 的整个工作流程,和 ThreadPoolExecutor 还是有很大的区别的,在这里我围绕 Fork/Join 仅阐明比较核心的几个概念:

2.Fork/Join工作方式

通常大家说的Fork/Join框架其实就是指由ForkJoinPool作为线程池ForkJoinTask(通常实现其三个抽象子类)为任务ForkJoinWorkerThread作为执行任务的具体线程实体这三者构成的任务调度机制

  • ForkJoinPoolForkJoinTask数组ForkJoinWorkerThread数组组成,ForkJoinTask数组负责存放提交给ForkJoinPool的任务,而ForkJoinWorkerThread负责执行这些任务
    • 任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部
    • 当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。

也就是说Fork/Join采用“工作窃取模式”,当执行新的任务时他可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程中偷一个并把它加入自己的队列中。

  • 就比如两个CPU上有不同的任务,这时候A已经执行完B还有任务等待执行,这时候A就会将B队尾的任务偷过来,加入A自己的队列中,对于传统的线程,ForkJoin更有效的利用的CPU资源!

通俗的说,ForkJoin框架的作用主要是为了实现将大型复杂任务进行递归的分解,直到任务足够小才直接执行,从而递归的返回各个足够小的任务的结果汇集成一个大任务的结果,依次类推最终得出最初提交的那个大型复杂任务的结果,这和方法的递归调用思想是一样的。当然ForkJoinPool线程池为了提高任务的并行度和吞吐量做了非常多而且复杂的设计实现,其中最著名的就是任务窃取机制

3.简述Fork/Join框架的3个核心类

3.1.ForkJoinPool

ForkJoinPool,执行任务的线程池

  • ForkJoinPool由ForkJoinTask数组ForkJoinWorkerThread数组组成,ForkJoinTask数组负责存放提交给ForkJoinPool的任务,而ForkJoinWorkerThread负责执行这些任务ForkJoinTask(编写任务执行具体逻辑)ForkJoinPool中的所有的工作线程ForkJoinWorkerThread均有一个自己的工作队列WorkQueue(内部类)存放要执行的任务:(下面会讲)
    【Java多线程】JUC之线程池(四)ForkJoin_第6张图片

3.2.ForkJoinWorkerThread

ForkJoinWorkerThread
是一种在Fork/Join框架中运行的特殊线程,它除了具有普通线程的特性外,最主要的特点是每一个ForkJoinWorkerThread线程都具有一个独立的任务等待队列(work queue),这个任务队列用于存储在本线程中被拆分的若干子任务
【Java多线程】JUC之线程池(四)ForkJoin_第7张图片

ForkJoinWorkerThread与 工作窃取算法

  • ForkJoinPool 的每个工作线程ForkJoinWorkerThread都维护着一个的双端(Deque)的工作队列(WorkQueue), 队列用于存储要执行的任务(ForkJoinTask)。并实现了工作窃取(work-stealing)算法来提高并发的效率。即:线程在完成本线程维护的工作队列中的任务后,可以从其他线程维护的工作队列中窃取任务来执行,避免空闲线程造成的资源浪费
    • 本线程ForkJoinWorkerThread维护的双端队列,任务的"出入队" 都是通过"队首"进行的
    • 工作线程ForkJoinWorkerThread每次从所维护工作队列的 “头部”取任务执行,当队列中没有任务可执行时,该线程会去其他线程维护队列的"尾部" 窃取任务执行。即:对于本线程维护的队列,任务执行的顺序是FIFO(先进先出),窃取其他线程队列任务的执行顺序是LIFO(后进先出)

看下工作窃取示意图:
【Java多线程】JUC之线程池(四)ForkJoin_第8张图片
本线程ForkJoinWorkerThread维护的双端队列,任务的 “出入队” 都是通过 “队首” 进行的Thread_1在本队列的 “队首” 取任务执行,Thread_2维护的队列为,此时它不会闲着,而是去Thread_1维护的队列 “队尾” 窃取任务来执行,通过示意图可以很清楚的了解其工作流程。

  • 提高了并发执行效率,本线程在队首取任务执行,其他线程窃取的时候在队尾取任务队列中只有一个任务的时候才会去争夺锁,其他情况下都不需要争夺锁。

3.3.ForkJoinTask

ForkJoinTask:主要包括两个方法分别实现任务的分拆合并

  1. fork()类似于Thread.start(),但是它并不是立即执行任务,而是将任务放入工作队列中
  2. join()Thread.join()不同,ForkJoinTask的join()方法并不是简单的阻塞线程;

三.ForkJoinPool

1.概述

Java7提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合成总的计算结果。 ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池

  • ForkJoinPool是执行ForkJoinTask的线程池,它在内部维护了一个ForkJoinThread数组,这些ForkJoinThread线程就是ForkJoinPool管理的线程。类似于ThreadPoolExecutor的工作线程Worker,我们将任务提交到ForkJoinPool之后,它维护的ForkJoinThread线程执行我们提交的任务。不同的是,ForkJoinPool会通过工作窃取算法让当前工作线程在完成维护的工作队列中的任务后,可以从其他线程维护的工作队列中窃取任务来执行,避免空闲线程造成的资源浪费这个调度是ForkJoinPool的核心功能

2.继承体系

【Java多线程】JUC之线程池(四)ForkJoin_第9张图片

3.ForkJoinPool成员变量

// Bounds
//低16位掩码,索引的最大位数,
static final int SMASK        = 0xffff;        // short bits == max index
//工作线程的最大容量
static final int MAX_CAP      = 0x7fff;        // max #workers - 1
//偶数低位掩码
static final int EVENMASK     = 0xfffe;        // even short bits
//偶数下标数,最多64个偶数下标(0x007e = 0111 1110,有效的是中间6个1的位置,111111 = 63,再加上000000(0下标),总共64个)
static final int SQMASK       = 0x007e;        // max 64 (even) slots

//WorkQueue的状态:正在扫描任务
static final int SCANNING     = 1;             // false when running tasks
//WorkQueue的状态:非活动状态
static final int INACTIVE     = 1 << 31;       // must be negative
//版本号(防止CAS的ABA问题)
static final int SS_SEQ       = 1 << 16;       // version count

// Mode bits for ForkJoinPool.config and WorkQueue.config
//模式掩码
static final int MODE_MASK    = 0xffff << 16;  // top half of int
//任务队列模式为LIFO
static final int LIFO_QUEUE   = 0;
//任务队列模式为FIFO
static final int FIFO_QUEUE   = 1 << 16;
//任务队列模式为共享模式
static final int SHARED_QUEUE = 1 << 31;       // must be negative


//线程工厂类
public static final ForkJoinWorkerThreadFactory
    defaultForkJoinWorkerThreadFactory;

//默认的公共线程池
static final ForkJoinPool common;

//并行度
static final int commonParallelism;

//最大备用线程数
private static int commonMaxSpares;

//线程变化序列号
private static int poolNumberSequence;

//ctl的低32位掩码
private static final long SP_MASK    = 0xffffffffL;
//ctl的高32位掩码
private static final long UC_MASK    = ~SP_MASK;

// Active counts
//活跃线程的计算shift
private static final int  AC_SHIFT   = 48;
//活跃线程的最小单位
private static final long AC_UNIT    = 0x0001L << AC_SHIFT;
//活跃线程数的掩码
private static final long AC_MASK    = 0xffffL << AC_SHIFT;

// Total counts
//工作线程shift
private static final int  TC_SHIFT   = 32;
//工作线程的最小单元
private static final long TC_UNIT    = 0x0001L << TC_SHIFT;
//工作线程掩码
private static final long TC_MASK    = 0xffffL << TC_SHIFT;
//创建工作线程的标记
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // sign

// runState bits: SHUTDOWN must be negative, others arbitrary powers of two
//线程池状态
//锁定
private static final int  RSLOCK     = 1;  
//通知
private static final int  RSIGNAL    = 1 << 1;
//开始
private static final int  STARTED    = 1 << 2;
//停止
private static final int  STOP       = 1 << 29;
//终止
private static final int  TERMINATED = 1 << 30;
//关闭
private static final int  SHUTDOWN   = 1 << 31;

// Instance fields
//线程池主控参数
volatile long ctl;                   // main pool control
//线程池运行状态
volatile int runState;               // lockable status
//并行度|模式
final int config;                    // parallelism, mode
//用于生成线程池的索引
int indexSeed;                       // to generate worker index
//工作队列池
volatile WorkQueue[] workQueues;     // main registry
//线程工厂
final ForkJoinWorkerThreadFactory factory;
//工作线程异常处理
final UncaughtExceptionHandler ueh;  // per-worker UEH
//工作线程名称的前缀
final String workerNamePrefix;       // to create worker name string
//偷取任务的总数
volatile AtomicLong stealCounter;    // also used as sync monitor
  • ForkJoinPool中有一个重要的成员变量 workQueues ,它是静态内部类WorkQueue类型的数组。WorkQueue类中有一个ForkJoinTask类型数组属性array 和一个ForkJoinWorkerThread类型的属性owner****, 其中 array数组负责存放提交给ForkJoinPool的任务,而owner负责执行当前WorkQueue中的任务。

4.ForkJoinPool状态说明(了解即可)

ctl参数说明
在这里插入图片描述
字段ctl是ForkJoinPool的核心状态,它是一个64位的long类型数值,包含4个16位子字段:

  • AC:当前活动工作线程数减去最大的工作线程数量(最大的工作线程数量,所以AC一般是负值,等于0时,说明活动线程已经达到饱和了)
  • TC: 总的工作线程数量总数减去最大的工作线程数量(TC一般也是负值,等于0时,说明总的工作线程已经饱和,并且,AC一般小于等于TC)
  • SS: 栈顶工作线程状态和版本数(每一个线程在挂起时都会持有前一个等待线程所在工作队列的索引,由此构成一个等待的工作线程栈,栈顶是最新等待的线程,第一位表示状态1.不活动 0.活动,后15表示版本号,标识ID的版本-最后16位)。
  • ID: 栈顶工作线程所在工作队列的池索引。

runState状态说明

  • STARTED 1
  • STOP 1 << 1
  • TERMINATED 1<<2
  • SHUTDOWN 1<<29
  • RSLOCK 1<<30
  • RSIGNAL 1<<31

runState记录了线程池的运行状态,除了SHUTDOWN是负数外,其他值都是正数,RSLOCK和RSIGNAL是跟锁相关

5.ForkJoinPool主要方法

5.1.构造函数

ForkJoinPool提供了如下3个常用的构造函数

	//创建一个包含parallelism个并行线程的ForkJoinPool
    public ForkJoinPool() {
        this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
             defaultForkJoinWorkerThreadFactory, null, false);
    }
    
	//以Runtime.getRuntime().availableProcessors()的返回值作为parallelism来创建ForkJoinPool
    public ForkJoinPool(int parallelism) {
        this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
    }
    
	//更加灵活的创建ForkJoinPool
    public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

	//私有构造器,上面3个都是基于当前这个构造函数初始化的
    private ForkJoinPool(int parallelism,
                         ForkJoinWorkerThreadFactory factory,
                         UncaughtExceptionHandler handler,
                         int mode,
                         String workerNamePrefix) {
        this.workerNamePrefix = workerNamePrefix;
        this.factory = factory;
        this.ueh = handler;
        this.config = (parallelism & SMASK) | mode;
        long np = (long)(-parallelism); // offset ctl counts
        this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
    }

构造ForkJoinPool可以指定如下4个参数:

  1. parallelism: 并行线程数、默认为CPU核心数,最小为1。为1的时候相当于单线程执行。

    但是千万不要将这个属性理解成Fork/Join框架中最大线程线程数量,也不要将这个属性和ThreadPoolExecutor线程池中的corePoolSize、maximumPoolSize属性进行比较, 因为ForkJoinPool的结构和工作方式与ThreadPoolExecutor完全不一样。而后续的讨论中,发现Fork/Join框架中可存在的线程数量和parallelism的关系并不是绝对的关联(有依据但并不全由它决定)

  2. factory:工作线程工厂,用于创建ForkJoinWorkerThread。默认情况下使用ForkJoinWorkerThreadFactory#defaultForkJoinWorkerThreadFactory
  3. handler:异常捕获处理器。当执行的任务中出现异常,并从任务中被抛出时,就会被handler捕获。默认为null
  4. asyncMode是否为异步模式默认是false 也就是LIFO

    这里的同步/异步并不是指Fork/Join框架本身是采用同步模式还是采用异步模式工作,而是指其中的工作线程的工作方式。即: 工作线程处理队列中等待执行任务,是采用先进先出的还是后进先出的工作模式。
    在Fork/Join框架中,每个工作线程(Worker)都有一个属于自己的任务队列(WorkQueue),这是一个底层采用数组实现的双向队列

    • 同步false:指对于工作线程(ForkJoinWorkerThread)自身队列中的任务,采用后进先出(LIFO)的方式执行;
    • 异步true:指对于工作线程(ForkJoinWorkerThread)自身队列中的任务,采用先进先出(FIFO)的方式执行。
    • 为true的异步模式只在不join任务结果的消息传递框架中非常有用,因此一般如果任务的结果需要通过join合并,则该参数都设为false。
  5. workerNamePrefix:顾名思义,工作线程名称前缀 默认为 “ForkJoinPool-" + nextPoolId() + "-worker-

5.2.提交任务

invoke/execute/submit任务提交

//提交任务并等待任务执行完成,然后返回执行结果
public <T> T invoke(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
    return task.join();
}

//只提交任务,无返回结果
public void execute(ForkJoinTask<?> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
}

//提交任务并返回任务,ForkJoinTask可获取任务的异步执行结果
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
    return task;
}

提交任务主要有3种方法,invoke(),execute(),submit(),它们最终都是调用externalPush()进行处理,都属于外部提交保存在偶数索引的工作队列

externalPush()添加任务:

final void externalPush(ForkJoinTask<?> task) {
    WorkQueue[] ws; WorkQueue q; int m;
    //探针值,用于计算WorkQueue索引
    int r = ThreadLocalRandom.getProbe();
    int rs = runState;
    if ((ws = workQueues) != null  //线程池不为空
        && (m = (ws.length - 1)) >= 0   //线程池长度大于0
        && (q = ws[m & r & SQMASK]) != null  //获取偶数索引的WorkQueue
        && r != 0 && rs > 0  //探针值不为0
        &&U.compareAndSwapInt(q, QLOCK, 0, 1)) { //加锁
        ForkJoinTask<?>[] a; int am, n, s;
        if ((a = q.array) != null //线程的任务队列不为空
            &&(am = a.length - 1) > (n = (s = q.top) - q.base)) { //任务数组长度大于数组中的任务个数,则无需扩容
            int j = ((am & s) << ASHIFT) + ABASE;  //计算任务的位置索引
                U.putOrderedObject(a, j, task);//将任务放入任务数组中
            U.putOrderedInt(q, QTOP, s + 1); //设置top为top+1
            U.putIntVolatile(q, QLOCK, 0); //解锁
            //若之前的任务数<=1,则此索引的线程可能在等待,同时可能其他索引的线程也在等,此时需要唤醒线程来执行任务
            if (n <= 1)
                signalWork(ws, q);
            return;
        }
        U.compareAndSwapInt(q, QLOCK, 1, 0); //添加任务失败,则解锁
    }
    //若if条件中有不满足的,或是添加任务失败,则通过externalSubmit()来添加任务
    externalSubmit(task);
}

signalWork()唤醒worker线程

final void signalWork(WorkQueue[] ws, WorkQueue q) {
    long c; int sp, i; WorkQueue v; Thread p;
    while ((c = ctl) < 0L) {     //活跃线程数太少,则创建工作线程
        if ((sp = (int)c) == 0) {    //无空闲线程?
            // (c & ADD_WORKER) != 0L,说明TC的最高位为1,为负值,而TC = 总的线程数 - 并行度 < 0,
            // 表示总的线程数 < 并行度,说明工作线程的个数还很少
            if ((c & ADD_WORKER) != 0L) 
                tryAddWorker(c); //尝试添加线程
            break;
        }
        //未开始或已停止
        if (ws == null)                            // unstarted/terminated
            break;
        // 空闲线程栈顶端线程的所属工作队列索引(正常来讲,应该小于WorkQueue[]的长度的)   
        if (ws.length <= (i = sp & SMASK))         // terminated
            break;
        //正则终止?      
        if ((v = ws[i]) == null)                   // terminating
            break;
        // 作为下一个scanState待更新的值(增加了版本号,并且调整为激活状态)   
        int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
        // 如果d为0,则说明scanState还为更新过,然后才考虑CAS ctl
        int d = sp - v.scanState;                  // screen CAS
        // 下一个ctl的值,AC + 1 | 上一个等待线程的索引
        long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
        if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
            v.scanState = vs;                      // activate v
            // 如果线程阻塞了,唤醒它
            if ((p = v.parker) != null)
                U.unpark(p);
            break;
        }
        // 没有任务,直接退出
        if (q != null && q.base == q.top)          // no more work
            break;
    }
}

externalSubmit()添加任务

包含了三方面的操作:

  • 若线程未初始化,则初始化线程池,长度是2的幂次方;
  • 若指定下标元素为空,则初始化一个共享模式的工作队列;
  • 若若指定下标元素不为空,则获取任务队列,并将任务提交到任务队列,成功则唤醒沉睡的线程;若失败则转移下标。
private void externalSubmit(ForkJoinTask<?> task) {
    int r;                                    // initialize caller's probe
    //初始化当前线程的探针值,用于计算WorkQueue的索引
    if ((r = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();
        r = ThreadLocalRandom.getProbe();
    }
    for (;;) {
        WorkQueue[] ws; WorkQueue q; int rs, m, k;
        boolean move = false;
        //线程池已经关闭?
        if ((rs = runState) < 0) {
            tryTerminate(false, false);     // help terminate
            throw new RejectedExecutionException();
        }
        //1、线程池状态为还未初始化?;
        //2、线程池为空?
        //3、线程池中工作线程数为0?
        else if ((rs & STARTED) == 0 ||     // initialize
                 ((ws = workQueues) == null || 
                 (m = ws.length - 1) < 0)) {
            int ns = 0;
            //加锁
            rs = lockRunState();
            try {
                //加锁后再次判断线程池状态,避免重复初始化
                if ((rs & STARTED) == 0) {
                    U.compareAndSwapObject(this, STEALCOUNTER, null,
                                           new AtomicLong());
                    // create workQueues array with size a power of two
                    int p = config & SMASK; // ensure at least 2 slots
                    //保证n是2的幂次方
                    int n = (p > 1) ? p - 1 : 1;
                    n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
                    n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
                    workQueues = new WorkQueue[n];
                    ns = STARTED;
                }
            } finally {
                //解锁
                unlockRunState(rs, (rs & ~RSLOCK) | ns);
            }
        }
        //获取随机偶数下标的WorkQueue的元素
        else if ((q = ws[k = r & m & SQMASK]) != null) {
            //对WorkQueue加锁
            if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
                ForkJoinTask<?>[] a = q.array;
                int s = q.top;
                boolean submitted = false; // initial submission or resizing
                try {                      // locked version of push
                    //若WorkQueue的任务队列为空,则初始化任务队列(growArray)
                    if ((a != null && a.length > s + 1 - q.base) ||
                        (a = q.growArray()) != null) {
                        //计算任务索引的下标    
                        int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
                        U.putOrderedObject(a, j, task);
                        U.putOrderedInt(q, QTOP, s + 1);
                        submitted = true;
                    }
                } finally {
                    //解锁
                    U.compareAndSwapInt(q, QLOCK, 1, 0);
                }
                //唤醒挂起的线程
                if (submitted) {
                    signalWork(ws, q);
                    return;
                }
            }
            move = true;                   // move on failure
        }
        //在未加锁的情况下,创建新线程
        else if (((rs = runState) & RSLOCK) == 0) { // create new queue
            q = new WorkQueue(this, null);
            q.hint = r;
            //共享模式
            q.config = k | SHARED_QUEUE;
            //未激活
            q.scanState = INACTIVE;
            //加锁
            rs = lockRunState();           // publish index
            if (rs > 0 &&  (ws = workQueues) != null &&
                k < ws.length && ws[k] == null)
                ws[k] = q;                 // else terminated
            //释放锁    
            unlockRunState(rs, rs & ~RSLOCK);
        }
        else
            move = true;                   // move if busy
        if (move)
            r = ThreadLocalRandom.advanceProbe(r);
    }
}

5.3.执行任务

runWorker()是在ForkJoinWorkerThread的run()方法中调用,即在启动worker线程调用的。其主要工作是获取任务并执行任务,若线程池关闭,则等待任务队列的任务执行完成并退出。

final void runWorker(WorkQueue w) {
    w.growArray();                   // allocate queue
    int seed = w.hint;               // initially holds randomization hint
    int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
    for (ForkJoinTask<?> t;;) {
        //扫描任务
        if ((t = scan(w, r)) != null)
            //工作线程执行任务
            w.runTask(t);
            //没有任务执行则等待
        else if (!awaitWork(w, r))
            break;
        //随机值更新    
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

scan()扫描任务

private ForkJoinTask<?> scan(WorkQueue w, int r) {
    WorkQueue[] ws; int m;
    //线程池不为空?
    if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
        int ss = w.scanState;                     // initially non-negative
        for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
            WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
            int b, n; long c;
            //下标为k的元素不为空,尝试从该任务队列里获取任务
            if ((q = ws[k]) != null) {
                //有任务
                if ((n = (b = q.base) - q.top) < 0 &&
                    (a = q.array) != null) {      // non-empty
                    //数组地址
                    long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    if ((t = ((ForkJoinTask<?>)
                              U.getObjectVolatile(a, i))) != null &&
                        q.base == b) {
                        //获取任务并更新base等索引信息    
                        if (ss >= 0) {
                            if (U.compareAndSwapObject(a, i, t, null)) {
                                q.base = b + 1;
                                //通知其他线程
                                if (n < -1)       // signal others
                                    signalWork(ws, q);
                                return t;
                            }
                        }
                        //设置WorkQueue的状态
                        else if (oldSum == 0 &&   // try to activate
                                 w.scanState < 0)
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                    }
                    if (ss < 0)                   // refresh
                        ss = w.scanState;
                    r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                    origin = k = r & m;           // move and rescan
                    oldSum = checkSum = 0;
                    continue;
                }
                checkSum += b;
            }
            //未扫描到任务,准备inactive此工作队列
            if ((k = (k + 1) & m) == origin) {    // continue until stable
                if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                    oldSum == (oldSum = checkSum)) {
                    if (ss < 0 || w.qlock < 0)    // already inactive
                        break;
                    int ns = ss | INACTIVE;       // try to inactivate
                    long nc = ((SP_MASK & ns) |
                               (UC_MASK & ((c = ctl) - AC_UNIT)));
                    w.stackPred = (int)c;         // hold prev stack top
                    U.putInt(w, QSCANSTATE, ns);
                    if (U.compareAndSwapLong(this, CTL, c, nc))
                        ss = ns;
                    else
                        w.scanState = ss;         // back out
                }
                checkSum = 0;
            }
        }
    }
    return null;
}

awaitWork()为等待任务。若工作线程未获取到任务,则会执行此方法。

private boolean awaitWork(WorkQueue w, int r) {
        if (w == null || w.qlock < 0) // w已经终止,返回false,不再扫描任务
            return false;
        for (int pred = w.stackPred, spins = SPINS, ss;;) { 
            if ((ss = w.scanState) >= 0) // 如果已经active,跳出,返回true,继续扫描任务
                break;
            else if (spins > 0) { // 如果spins > 0,自旋等待
                r ^= r << 6;
                r ^= r >>> 21;
                r ^= r << 7;
                if (r >= 0 && --spins == 0) { // 随机消耗自旋次数
                    WorkQueue v;
                    WorkQueue[] ws;
                    int s, j;
                    AtomicLong sc;
                    if (pred != 0 // 除了自己,还有等待的线程-工作队列
                            && (ws = workQueues) != null // 线程池还在
                            && (j = pred & SMASK) < ws.length // 前任索引还在池范围内
                            && (v = ws[j]) != null // 前任任务队列还在
                            && (v.parker == null || v.scanState >= 0)) // 前任线程已经唤醒,且工作队列已经激活
                        spins = SPINS; // 上面的一系列判断表明,很快就有任务了,先不park,继续自旋
                }
            } else if (w.qlock < 0) // 自旋之后,再次检查工作队列是否终止,若是,退出扫描
                return false;
            else if (!Thread.interrupted()) { // 如果线程中断了,清除中断标记,不考虑park,否则进入该分支
                long c, prevctl, parkTime, deadline;
                int ac = (int) ((c = ctl) >> AC_SHIFT) + (config & SMASK); // 计算活跃线程的个数
                if ((ac <= 0 && tryTerminate(false, false)) || (runState & STOP) != 0) // 线程池正在终止,退出扫描
                    return false;
                if (ac <= 0 && ss == (int) c) { // 自己是栈顶等待者
                    prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred); // 设置为前一次的ctl
                    int t = (short) (c >>> TC_SHIFT); // 总的线程数
                    if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl)) // 总线程数过多,直接退出扫描
                        return false;
                    parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t); // 计算等待时间
                    deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
                } else
                    prevctl = parkTime = deadline = 0L;
                Thread wt = Thread.currentThread();
                U.putObject(wt, PARKBLOCKER, this); // 加锁
                w.parker = wt; // 设置parker,准备阻塞
                if (w.scanState < 0 && ctl == c) // 阻塞前再次检查状态
                    U.park(false, parkTime);
                U.putOrderedObject(w, QPARKER, null); // 唤醒后,置空parker
                U.putObject(wt, PARKBLOCKER, null); // 解锁
                if (w.scanState >= 0) // 已激活,跳出继续扫描
                    break;
                if (parkTime != 0L && ctl == c && deadline - System.nanoTime() <= 0L
                        && U.compareAndSwapLong(this, CTL, c, prevctl)) // 超时,未等到任务,跳出,不再执行扫描任务,削减工作线程
                    return false; // shrink pool
            }
        }
        return true;
    }

6.工作线程-ForkJoinWorkerThread解析

ForkJoinWorkerThread是运行在ForkJoinPool中的线程,它内部会维护一个存放ForkJoinTask类型的WorkQueue队列,而WorkQueue是ForkJoinPool的内部类。其关联了对应的ForkJoinPoolWorkQueue

构造函数
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);
}

执行任务的钩子函数:

//线程启动时的回调函数
protected void onStart() {}
//线程结束的回调函数
protected void onTermination(Throwable exception) {}

执行任务

public void run() {
    if (workQueue.array == null) { 
        Throwable exception = null;
        try {
            //线程开始回调函数
            onStart();
            //执行任务
            pool.runWorker(workQueue);
        } catch (Throwable ex) {
            exception = ex;
        } finally {
            try {
                //线程终止回调函数
                onTermination(exception);
            } catch (Throwable ex) {
                if (exception == null)
                    exception = ex;
            } finally {
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

//ForkJoinPool#runWorker()
final void runWorker(WorkQueue w) {
    w.growArray();                   // allocate queue
    int seed = w.hint;               // initially holds randomization hint
    int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
    for (ForkJoinTask<?> t;;) {
        //扫描任务,并执行任务
        if ((t = scan(w, r)) != null)
            w.runTask(t);
        //等待窃取任务  
        else if (!awaitWork(w, r))
            break;
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

7.工作队列-WorkQueue

WorkQueue为ForkJoinPool的内部类,结构为双端队列Dueue其封装提交的任务ForkJoinTask线程池ForkJoinPool执行线程ForkJoinWorkerThread、及其他任务相关数据等。

  • 每个工作线程ForkJoinWorkerThread 都有自己的双端工作队列WorkQueue,它是线程私有的
  • ForkJoinTask中fork()的子任务,将放入运行该任务对应的工作线程的 "队头"
  • 为了最大化地利用CPU,空闲的线程将从其它线程的队列尾部窃取任务来执行,以减少竞争
  • 双端队列的操作:push()/pop()仅在其所有者工作线程中调用,poll()是由其它线程窃取任务时调用的;
  • 当只剩下最后一个任务时,还是会存在竞争,是通过CAS来实现的;

为啥要双端队列呢?

  • 因为ForkJoinPool有一个机制,当某个工作线程对应的工作队列为空时就会去别的工作线程的任务队列的“尾部”窃取任务执行。而被窃取任务的线程还是从任务队列的 “头部”取出任务进行执行。这样双端就井然有序,不会有任务争抢的情况。
/**
 *如果WorkQueue没有属于自己的owner(下标为偶数的都没有),该值为 inactive 也就是一个负数;
 *如果有自己的owner,该值的初始值为其在WorkQueue[]数组中的下标,也肯定是个奇数;
 * 如果这个值,变成了偶数,说明该队列所属的Thread正在执行Task。
 */
volatile int scanState;    
//前任池(WorkQueue[])索引,由此构成一个栈;
int stackPred; 
//窃取的次数   
int nsteals;          
//用于窃取线程进行随机选择被窃取的初始化索引的计算值   
int hint;                 
//index | mode。 如果下标为偶数的WorkQueue,则其mode是共享类型。如果有自己的owner 默认是 LIFO     
int config;              
//锁标识,在多线程往队列中添加数据,会有竞争,使用此标识抢占锁。1: locked, < 0: terminate; else 0   
volatile int qlock;      
 // 下一个出队poll元素的索引(主要是为线程窃取准备的索引位置)
volatile int base;        
 // 下一个入队pull元素准备的索引
int top;       
//负责存放提交给ForkJoinPool的任务
ForkJoinTask<?>[] array;  
// 队列所属的ForkJoinPool(可能为空)
// 注意,一个ForkJoinPool中会有多个执行线程,还会有比执行线程更多的(或一样多的)队列 
final ForkJoinPool pool;  
//工作队列的所属工作线程,如果为共享任务队列则没有所有者,这个值为空
final ForkJoinWorkerThread owner; 
//owoner线程在调用过程中如果出现park阻塞,则这个变量指向owoner,反之为空
volatile Thread parker;   
//当前正在join等待结果的任务。
volatile ForkJoinTask<?> currentJoin;
// 当前正在偷取的任务
volatile ForkJoinTask<?> currentSteal;

主要方法说明

1.入队方法

当工作队列WorkQueue的所属工作线程ForkJoinWorkerThread需要向双端队列中放入一个新的待执行子任务ForkJoinTask时,会调用WorkQueue中的push方法。

//工作线程将任务提交到其对应的工作队列中
final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
     // base: 下一个出队元素的索引位(主要是为线程窃取准备的索引位置)
	// top: 下一个入队元素准备的索引位
    //判断array不为空
    if ((a = array) != null) {   
        //m为最高为位置的index
        int m = a.length - 1;     
        //将task采用cas的方式,put到数组中的top+1的位置,下面代码的一大堆操作实际上是与cas相关的,需要计算类对象中的偏移量,如果我们不用usafe类,那么这个地方就会非常简单。(计算放置任务的位置,并将任务保存到队列中)
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
        //采用cas的方式,将top的指针加1(将workQueue对象本身中的top标识的位置 + 1)
        U.putOrderedInt(this, QTOP, s + 1);
        //如果n小于等于1则 且poll不为空 则触发worker窃取或者产生新的worker
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        //如果n大于等于了m 则说明需要扩容了(队列已满,则进行扩容)
        else if (n >= m)
            growArray();
    }
}
2.出队方法

当ForkJoinWorkerThread需要从双端队列中取出下一个待执行子任务,就会根据初始化ForkJoinPool 设置的asyncMode调用双端队列的不同方法,代码概要如下所示:

//将从队列中按FIFO的方式取出task
final ForkJoinTask<?> pop() {
   ForkJoinTask<?>[] a; ForkJoinTask<?> t; int m;
   // 如果array不为空且长度大于0
   if ((a = array) != null && (m = a.length - 1) >= 0) {
    //循环,s为top的下标减1,即top减1之后要大于0 也就是说要存在task
       for (int s; (s = top - 1) - base >= 0;) {
        //计算unsafe的偏移量 得到s的位置
           long j = ((m & s) << ASHIFT) + ABASE;
           //数组下标j处有任务,则cas获取获取任务,并修改top值
           if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
               break;
           //反之用usafe的方法将这个值取走,之后返回,并更新top的指针
           if (U.compareAndSwapObject(a, j, t, null)) {
               U.putOrderedInt(this, QTOP, s);
               return t;
           }
       }
   }
   return null;
}

//从base到top获取任务 (将从队列中按FIFO的方式取出task,并移除)
final ForkJoinTask<?> poll() {
   ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
   //判断 base-top小于0说明存在task 切array不为空
   while ((b = base) - top < 0 && (a = array) != null) {
      //计算出unsafe操作的索引 实际上就是拿到b
       int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
        //之后拿到这个task 用volatile的方式
       t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
       //之后如果base和b相等
       if (base == b) {
         //如果拿到的task不为空
           if (t != null) {
               //cas更改base处的数据,将这个位置的元素移除,同时base+1 然后返回t
               if (U.compareAndSwapObject(a, j, t, null)) {
                   base = b + 1;
                   return t;
               }
           }
           //任务全部取完? ->在上述操作之后,如果base比top小1说明已经为空了 直接退出循环
           else if (b + 1 == top) // now empty
               break;
       }
   }
   return null;
}

//peek则根据之前的mode定义,从队列的前面或者后面取得task。
final ForkJoinTask<?> peek() {
   ForkJoinTask<?>[] a = array; int m;
     //判断数组的合法性
   if (a == null || (m = a.length - 1) < 0)
       return null;
   //判断任务队列是FIFO或LILF?   ->根据mode决定从top还是base处获得task
   int i = (config & FIFO_QUEUE) == 0 ? top - 1 : base;
   int j = ((i & m) << ASHIFT) + ABASE;
   //获取指定顶部或底部的任务
   return (ForkJoinTask<?>)U.getObjectVolatile(a, j);
}
3.执行任务
final void execLocalTasks() {
    int b = base, m, s;
    ForkJoinTask<?>[] a = array;
    if (b - (s = top - 1) <= 0 && a != null &&
        (m = a.length - 1) >= 0) {
        //队列类型为FIFO?    
        if ((config & FIFO_QUEUE) == 0) {
            //遍历任务并执行
            for (ForkJoinTask<?> t;;) {
                if ((t = (ForkJoinTask<?>)U.getAndSetObject
                     (a, ((m & s) << ASHIFT) + ABASE, null)) == null)
                    break;
                U.putOrderedInt(this, QTOP, s);
                t.doExec();
                if (base - (s = top - 1) > 0)
                    break;
            }
        }
        else
            pollAndExecAll();
    }
}

//执行任务
final void runTask(ForkJoinTask<?> task) {
    if (task != null) {
        //设置WorkQueue状态为执行任务状态
        scanState &= ~SCANNING; // mark as busy
        //执行窃取的任务
        (currentSteal = task).doExec();
        U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
        //执行所有本地
        execLocalTasks();
        ForkJoinWorkerThread thread = owner;
        if (++nsteals < 0)      // collect on overflow
            transferStealCount(pool);
        scanState |= SCANNING;
        if (thread != null)
            thread.afterTopLevelExec();
    }
}

参考WorkQueue源码

四.ForkJoinTask

1.概述

创建ForkJoinPool实例后,可以调用ForkJoinPool的submit(ForkJoinTask task)或者invoke(ForkJoinTask task)来提交指定任务。

  • ForkJoinTask是ForkJoinPool执行的任务,要使用ForkJoin框架,必须创建ForkJoinTask任务。该类提供了将任务分割成子任务的方法fork()等待子任务完成的方法join(),通常情况下,我们将一个大的任务fork()成两个子任务,再通过join()等待子任务完成。

2.继承体系

【Java多线程】JUC之线程池(四)ForkJoin_第10张图片

ForkJoinTask表示一个可以并行、合并的任务ForkJoinTask是一个抽象类,它有3个抽象子类、2个主要的方法。

3个抽象子类

  • RecursiveTask代表有返回值的任务
  • RecursiveAction代表没有返回值的任务。
  • CountedCompleter 代表 在任务完成执行后,触发自定义的回调函数无返回值

2个主要的方法

  • fork()方法类似于线程的Thread.start()方法,但是它不是真的启动一个线程,而是将任务放入到工作队列中。
  • join()方法类似于线程的Thread.join()方法,但是它不是简单地阻塞线程,当前工作线程对应的工作队列为空时,将去其他工作线程的工作队列中窃取任务执行。

3.如何使用

在实际的使用中,一般步骤为:

1.声明 ForkJoinPool

   ForkJoinPool forkJoinPool = new ForkJoinPool();

2.继承 ForkJoinTask 抽象类或其子类,重写其子类的compute方法

public class RecursiveTaskDemo extends RecursiveTask<Integer> {//xxxxx}

3.在继承 ForkJoinTask后重写的compute()方法实现子任务的切分fork()和结果的合并join()

②ForkJoinPool : ForkJoinTask任务需要执行器ForkJoinPool来执行。ForkJoinPool是ExecutorService的实现类,它代表一个可执行任务的执行器。

【Java多线程】JUC之线程池(四)ForkJoin_第11张图片
任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。


3.1.使用RecursiveAction(无返回值)

public abstract class RecursiveAction extends ForkJoinTask<Void> {
 
    /** 无返回值【我们自己的回调实现】 */
    protected abstract void compute();
 
    protected final boolean exec() {
        compute();
        return true;
    }
}

下面以一个没有返回值的大任务为例,介绍一下RecursiveAction的用法。

  • 大任务是:打印0-100的数值。小任务是:每次只能打印20个数值。
/**
 * 无返回值的任务
 */
public class RecursiveActionDemo extends RecursiveAction {
    /**
     *临界值: 每个"小任务"最多只打印20个数
     */
    private static final int MAX = 20;
    private static final long serialVersionUID = -2484809508247272346L;

    private int start;//起始值
    private int end;//结束值

    public RecursiveActionDemo(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        //当end-start的值小于MAX时,开始打印(判断是否是拆分完毕,如果任务足够小了,可以直接计算)
        if ((end - start) < MAX) {
            for (int i = start; i < end; i++) {
                System.out.println(Thread.currentThread().getName() + "=>i的值" + i);
            }
            return;
        }

        //大于阀值需要继续分割任务
        // 将大任务分解成两个小任务( 二分法,计算中间值,分成左右两段)
        System.out.println(Thread.currentThread().getName() + "任务分解start=================");
        int middle = (start + end) / 2;
        System.out.println(Thread.currentThread().getName() + "===>(" + start + " + " + end + ")/2=" + middle);
        System.out.println(Thread.currentThread().getName() + "===>left(" + start + "," + middle + ")");
        System.out.println(Thread.currentThread().getName() + "===>right(" + middle + "," + end + ")");
        RecursiveActionDemo left = new RecursiveActionDemo(start, middle);
        RecursiveActionDemo right = new RecursiveActionDemo(middle, end);
        System.out.println(Thread.currentThread().getName() + "任务分解end=================");
        //fork方法:分别执行左右两段任务(若任务不够小,将递归调用compute)
        left.fork();//拆分,并压入线程队列
        right.fork();//拆分,并压入线程队列
        //方式二: invokeAll(left,right);
    }

    public static void main(String[] args) throws Exception {
        // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // 提交可分解的PrintTask任务
        forkJoinPool.submit(new RecursiveActionDemo(0, 1000));

        //阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
        forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);

        // 关闭线程池
        forkJoinPool.shutdown();
    }
}

执行结果

【Java多线程】JUC之线程池(四)ForkJoin_第12张图片

  • 从上面结果来看,ForkJoinPool启动了12个线程来执行这个打印任务,我的计算机的CPU是四核的。大家还可以看到程序虽然打印了0-999这一千个数字,但是并不是连续打印的,这是因为程序将这个打印任务进行了分解,分解后的任务会并行执行,所以不会按顺序打印。

3.2.使用RecursiveTask(有返回值)

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
 
    V result;
 
    /** 有返回值【自己的回调函数】 */
    protected abstract V compute();
 
    // 回调上面的任务
    protected final boolean exec() {
        result = compute();
        return true;
    }

下面以一个有返回值的大任务为例,介绍一下RecursiveTask的用法。

  • 大任务是:计算随机的1000个数字的和。小任务是:每次只能70个数值的和。
public class RecursiveTaskDemo extends RecursiveTask<Integer> {

    /**
     * 临界值: 每个"小任务"最多只打印70个数
     */
    private static final int MAX = 70;
    private static final long serialVersionUID = 2362887250249162711L;
    private int arr[];
    private int start;//起始值
    private int end;//结束值


    public RecursiveTaskDemo(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

 
    @Override
    protected Integer compute() {
        int sum = 0;
        // 当end-start的值小于MAX时候,(判断是否是拆分完毕,如果任务足够小了,可以直接计算)
        if ((end - start) < MAX) {
            for (int i = start; i < end; i++) {
                sum += arr[i];
            }
            return sum;
        }

        //大于阀值需要继续分割任务
        // 将大任务分解成两个小任务
        System.out.println(Thread.currentThread().getName() + "任务分解start=================");

        // 将大任务分解成两个小任务(二分法,计算中间值,分成左右两段)
        int middle = (start + end) / 2;
        System.out.println(Thread.currentThread().getName() + "===>(" + start + " + " + end + ")/2=" + middle);
        System.out.println(Thread.currentThread().getName() + "===>left(" + start + "," + middle + ")");
        System.out.println(Thread.currentThread().getName() + "===>right(" + middle + "," + end + ")");
        RecursiveTaskDemo left = new RecursiveTaskDemo(arr, start, middle);
        RecursiveTaskDemo right = new RecursiveTaskDemo(arr, middle, end);
        // fork方法:分别执行左右两段任务(若任务不够小,将递归调用compute)
        // 并行执行两个小任务
        left.fork();//拆分,并压入线程队列
        right.fork();//拆分,并压入线程队列
        //方式二:invokeAll(left,right);
        // 把两个小任务累加的结果合并起来
        // 等待左右两段任务执行完,再获取子任务的结果
        return left.join() + right.join();
    }

   public static void main(String[] args) throws ExecutionException, InterruptedException {
        int[] arr = new int[1000];
        int total = 0;
        // 初始化100个数字元素
        for (int i = 0; i < arr.length; i++) {
            int temp = i;
            // 对数组元素赋值,并将数组元素的值添加到total总和中
            total += (arr[i] = temp);
        }
        System.out.println("初始化时的总和=" + total);

        // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // 提交可分解的任务
        /*Future future = forkJoinPool.submit(new RecursiveTaskDemo(arr, 0, arr.length));
        System.out.println("计算出来的总和="+future.get());*/

        Integer integer = forkJoinPool.invoke(new RecursiveTaskDemo(arr, 0, arr.length));
        System.out.println("计算出来的总和=" + integer);

        // 关闭线程池
        forkJoinPool.shutdown();
    }
}

执行结果

初始化时的总和=499500
ForkJoinPool-1-worker-9任务分解start=================
ForkJoinPool-1-worker-9===>(0 + 1000)/2=500
ForkJoinPool-1-worker-9===>left(0,500)
ForkJoinPool-1-worker-9===>right(500,1000)
ForkJoinPool-1-worker-11任务分解start=================
ForkJoinPool-1-worker-2任务分解start=================
ForkJoinPool-1-worker-11===>(500 + 1000)/2=750
ForkJoinPool-1-worker-2===>(0 + 500)/2=250
ForkJoinPool-1-worker-11===>left(500,750)
ForkJoinPool-1-worker-2===>left(0,250)
ForkJoinPool-1-worker-11===>right(750,1000)
ForkJoinPool-1-worker-2===>right(250,500)
ForkJoinPool-1-worker-4任务分解start=================
ForkJoinPool-1-worker-4===>(500 + 750)/2=625
ForkJoinPool-1-worker-4===>left(500,625)
ForkJoinPool-1-worker-4===>right(625,750)
ForkJoinPool-1-worker-6任务分解start=================
ForkJoinPool-1-worker-11任务分解start=================
ForkJoinPool-1-worker-15任务分解start=================
ForkJoinPool-1-worker-13任务分解start=================
ForkJoinPool-1-worker-6===>(750 + 1000)/2=875
ForkJoinPool-1-worker-13===>(0 + 250)/2=125
ForkJoinPool-1-worker-8任务分解start=================
ForkJoinPool-1-worker-15===>(250 + 500)/2=375
ForkJoinPool-1-worker-11===>(500 + 625)/2=562
ForkJoinPool-1-worker-15===>left(250,375)
ForkJoinPool-1-worker-8===>(625 + 750)/2=687
ForkJoinPool-1-worker-13===>left(0,125)
ForkJoinPool-1-worker-6===>left(750,875)
ForkJoinPool-1-worker-13===>right(125,250)
ForkJoinPool-1-worker-8===>left(625,687)
ForkJoinPool-1-worker-10任务分解start=================
ForkJoinPool-1-worker-15===>right(375,500)
ForkJoinPool-1-worker-11===>left(500,562)
ForkJoinPool-1-worker-1任务分解start=================
ForkJoinPool-1-worker-15任务分解start=================
ForkJoinPool-1-worker-10===>(125 + 250)/2=187
ForkJoinPool-1-worker-8===>right(687,750)
ForkJoinPool-1-worker-13任务分解start=================
ForkJoinPool-1-worker-6===>right(875,1000)
ForkJoinPool-1-worker-13===>(0 + 125)/2=62
ForkJoinPool-1-worker-10===>left(125,187)
ForkJoinPool-1-worker-15===>(250 + 375)/2=312
ForkJoinPool-1-worker-1===>(375 + 500)/2=437
ForkJoinPool-1-worker-11===>right(562,625)
ForkJoinPool-1-worker-1===>left(375,437)
ForkJoinPool-1-worker-15===>left(250,312)
ForkJoinPool-1-worker-10===>right(187,250)
ForkJoinPool-1-worker-13===>left(0,62)
ForkJoinPool-1-worker-8任务分解start=================
ForkJoinPool-1-worker-12任务分解start=================
ForkJoinPool-1-worker-8===>(875 + 1000)/2=937
ForkJoinPool-1-worker-13===>right(62,125)
ForkJoinPool-1-worker-15===>right(312,375)
ForkJoinPool-1-worker-1===>right(437,500)
ForkJoinPool-1-worker-8===>left(875,937)
ForkJoinPool-1-worker-8===>right(937,1000)
ForkJoinPool-1-worker-12===>(750 + 875)/2=812
ForkJoinPool-1-worker-12===>left(750,812)
ForkJoinPool-1-worker-12===>right(812,875)
计算出来的总和=499500
  • 从上面结果来看,ForkJoinPool将任务分解了15次,程序通过SumTask计算出来的结果,和初始化数组时统计出来的总和是相等的,这表明计算结果一切正常。

从上面的示例可看出使用Fork/Join框架的关键在于实现compute方法 。在compute方法中,我们首先要确定任务是否需要继续分割,如果任务足够小、满足预先设定的阀值就可直接执行任务。如果任务仍然很大,就必须继续分割成两个子任务,每个子任务在调用fork方法时,又会进入compute方法,看看当前子任务是否需要继续分割成子任务,如果不需要继续分割,则执行当前子任务并返回结果,使用join方法会等待子任务执行完并得到其结果。

4.主要方法

4.1.任务状态

和很多其他JUC框架类似,ForkJoinTask也有自己的任务执行状态

//statue用于保存ForkJoinTask的任务状态,默认为0,表示任务初始状态为(正在执行状态),不需要等待子任务完成
volatile int status; 
// 任务状态的掩码
static final int DONE_MASK   = 0xf0000000;  
//正常状态,-1,负数,表示任务“正常”完成的状态。
static final int NORMAL      = 0xf0000000;
// 任务取消,-2 < NORMAL  表示任务“取消”完成的状态
static final int CANCELLED   = 0xc0000000;  
// 任务异常,-3,
static final int EXCEPTIONAL = 0x80000000;  
// 通知状态,1 有其他任务依赖当前任务,任务结束前,通知其他任务join当前任务的结果。
static final int SIGNAL      = 0x00010000;  
// 低位掩码
static final int SMASK       = 0x0000ffff;  

status是一个volatile变量,表示当前任务的执行状态,它有4个状态,负数表示该任务已经执行完成非负数表示任务还没有执行完成

  • 其中已经执行完成状态包括NORMAL、CANCELLED、EXCEPTIONAL3种状态
  • 未完成状态包括初始状态0和SIGNAL

4.2.fork

fork()方法用于: 将新创建的子任务放入ForkJoinWorkerThread线程的工作队列,异步执行,立即返回,此方法不会阻塞

  • ForkJoinPool将根据当前正在执行ForkJoinTask任务的ForkJoinWorkerThread线程状态,决定是让这个任务在队列中等待,还是创建一个新的ForkJoinWorkerThread线程运行它,又或者是唤起其它正在等待任务的ForkJoinWorkerThread线程运行它

当我们调用ForkJoinTask的fork方法时,程序会把任务放在ForkJoinWorkerThread的pushTask的workQueue中,异步地执行这个任务,然后立即返回结果。

public final ForkJoinTask<V> fork() {
        Thread t;     //如果当前线程是ForkJoinWorkerThread,将其提交到关联的WorkQueue中
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else      //否则调用common池的externalPush方法入队
            ForkJoinPool.common.externalPush(this);
        return this;
    }
    
    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }

push方法:把当前任务存放在ForkJoinTask数组队列里。然后再调用ForkJoinPool的signalWork()方法 唤醒或创建一个工作线程来执行任务 代码如下:

        final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; ForkJoinPool p;
            int b = base, s = top, n;
            if ((a = array) != null) {    // ignore if queue removed
                int m = a.length - 1;     // fenced write for task visibility
                U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);//关键代码1
                U.putOrderedInt(this, QTOP, s + 1);//关键代码2
                if ((n = s - b) <= 1) {
                    if ((p = pool) != null)
                        p.signalWork(p.workQueues, this);//关键代码3
                }
                else if (n >= m)
                    growArray();
            }
        }

4.3.join / quietlyJoin

join方法用于让当前线程阻塞,直到对应的子任务完成运行并返回执行结果。其目的是尽快得到当前子任务的运行结果,然后继续执行。


  • join()方法用于阻塞当前线程,等待子任务执行完成,部分场景下会通过当前线程执行任务,如果异常结束或者被取消需要抛出异常
  • quietlyJoin()方法只是阻塞当前线程等待任务执行完成,不会抛出异常

源码如下:

	public final V join() {
        int s;
         //调用doJoin方法阻塞等待的结果不是NORMAL,即非正常完成,说明有异常或取消.,则根据状态抛出不同的异常.  
	    //	如果状态为CANCELLED,则抛出CancellationException(),异常;
    	//	如果状态为EXCEPTIONAL,则抛出包装后的异常
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);     //如果不是正常完成的,则报告异常
        return getRawResult(); //返回执行结果,该方法是抽象方法    
    }


	public final void quietlyJoin() {
        doJoin(); //只是等待任务执行完成
    }

	private void reportException(int s) {
        if (s == CANCELLED)
            //被取消了
            throw new CancellationException();
        if (s == EXCEPTIONAL)
            //重新抛出异常
            rethrow(getThrowableException());
	}

	static void rethrow(Throwable ex) {
        if (ex != null)
            ForkJoinTask.<RuntimeException>uncheckedThrow(ex);
    }
 
	@SuppressWarnings("unchecked") static <T extends Throwable>
        void uncheckedThrow(Throwable t) throws T {
        throw (T)t; // rely on vacuous cast
    }


它首先调用doJoin方法,通过doJoin()方法得到当前任务ForkJoinTask的状态来判断返回什么结果,ForkJoinTask的状态有4种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常(EXCEPTIONAL)

  • 如果任务状态是已完成(NORMAL),则直接返回任务结果。
  • 如果任务状态是被取消(CANCELLED),则直接抛出CancellationException
  • 如果任务状态是抛出异常(EXCEPTIONAL),则直接抛出对应的异常
  • 如果没有返回状态,则使用当线程池所在的ForkJoinPool的awaitJoin方法等待.

doJoin方法的实现

 //1、若任务状态为正常完成(status < 0),则返回任务的正常完成状态;
 //2、若执行任务的当前线程类型为ForkJoinWorkerThread,且将任务从线程的工作队列中出队成功,
 //		则调用doExec()执行任务,若任务执行状态为正常结束,则返回状态,否则awaitJoin()等待任务结束。
 //3、否则调用externalAwaitDone()等待任务执行完成。
private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;//已完成,返回status,未完成再尝试后续
        //若任务状态为正常完成(status < 0),则返回任务的正常完成状态
        return (s = status) < 0 ? s :      
			  //如果status大于等于0
			//未完成,当前线程是ForkJoinWorkerThread,从该线程中取出workQueue,并尝试将当前task出队然后执行,执行的结果是完成则返回状态,否则使用当线程池所在的ForkJoinPool的awaitJoin方法等待
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
             //awaitJoin也是阻塞当前线程,直到任务执行完成
            wt.pool.awaitJoin(w, this, 0L) : 
             //当前线程不是ForkJoinWorkerThread,调用externalAwaitDone方法.      
            externalAwaitDone();
    }

  //执行任务,doExec方法的返回值取决于exec方法,如果exec返回true,则doExec返回值小于0
  //如果返回false,则doExec返回值大于等于0
  final int doExec() {
        int s; boolean completed;
        //任务未完成
        if ((s = status) >= 0) {
            try {
            	//执行任务,exec是一个抽象方法,为了扩展,留给子类实现
            	// exec()返回任务是否正常完成
                completed = exec();
            } catch (Throwable rex) {
            	//任务执行抛出异常,设置任务异常完成状态
                return setExceptionalCompletion(rex);
            }
            //任务务正常完成,设置正常完成状态,通知其他需要join该任务的线程
            if (completed)
                s = setCompletion(NORMAL);
        }
        return s;
    }


    //-------------------------翻译后的doJoin方法---------------------
    private int doJoin() {
        int s;Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
        if ((s = status) < 0) {//任务已完成,直接返回state
            return s;
        } else {
            if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) { //当前线程是ForkJoinWorkerThread线程时
                wt = (ForkJoinWorkerThread) t;
                w = wt.workQueue;
                //tryUnpush取出这个任务
                //doExec准备执行exec方法(exec又调用compute方法),并(若完成)记录状态,但是doExce方法不会等待任务执行完成
                if (w.tryUnpush(this) && (s = doExec()) < 0){ 
                    return s;
                }else{
                    return  wt.pool.awaitJoin(w, this, 0L)//等待任务执行完成
                }
            }else{
                return externalAwaitDone();//当前线程是普通线程,调用externalAwaitDone阻塞当前线程,等待任务完成
            }
        }
    }

doJoin方法等待该任务完成,返回完成时的状态(NORMAL、CANCELLED、EXCEPTIONAL)。

  • 首先通过任务的状态判断任务是否已经执行完成,如果执行完成,则直接返回任务状态
  • 如果没有执行完
    • 如果执行当前任务的线程不是ForkJoinWorkerThread,调用externalAwaitDone方法等待任务执行完成。
    • 否则,从当前线程维护的队列取队首的任务,如果队首的任务不是当前的任务或者任务未完成,调用当前线程的joinTask方法将当前任务加入到等待队列并等待该任务执行完成。 如果任务顺利执行完成,则设置任务状态为NORMAL,如果出现异常,则记录异常,并将任务状态设置为EXCEPTIONAL
  • exec是一个抽象方法,完成具体任务的代码,由子类实现,该方法返回任务是否正常完成。任务执行过程如果抛出异常,捕获异常并设置异常完成状态。如果任务正常完成,设置正常状态并通知其他需要join该任务的线程,其他需要join该任务的线程通常是一个等待父任务完成的线程,也就是说,此时当前任务其实是个子任务,子任务结束后,父任务就可以尝试合并子任务的执行结果了,看下示例图:
    【Java多线程】JUC之线程池(四)ForkJoin_第13张图片

join 的整体流程如下:

【Java多线程】JUC之线程池(四)ForkJoin_第14张图片

4.4.externalAwaitDone

externalAwaitDone().它体现了ForkJoin框架的一个核心:外部帮助,

  • 逻辑不复杂,在当前task为ForkJoinPool.common的情况下可以在外部进行等待和尝试帮助完成.
  • 方法会首先根据ForkJoinTask的类型进行尝试帮助,并返回当前的status,若发现未完成,则进入下面的等待唤醒逻辑
  • 该方法的调用者为非worker线程(非工作线程).
//阻塞普通Java线程等待任务执行完成
 private int externalAwaitDone() {
        int s = ((this instanceof CountedCompleter) ? // try helping
                 //如果是CountedCompleter,则通过externalHelpComplete方法阻塞当前线程等待任务完成
                 ForkJoinPool.common.externalHelpComplete(
                     (CountedCompleter<?>)this, 0) :
                 //如果是普通的ForkJoinTask,则通过tryExternalUnpush尝试将其从任务队列中pop出来,如果该任务位于任务队列顶端则pop成功并返回true
                 //pop成功后执行doExec方法,即通过当前线程完成任务
                 ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
        if (s >= 0 && (s = status) >= 0) {
            boolean interrupted = false;
            do {
                //修改status,加上SIGNAL标识,表示有线程等待了
                if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                    synchronized (this) {
                        if (status >= 0) { //再次校验状态
                            try {
                                //0表示无期限等待,直到被唤醒
                                //任务执行完成,可通过setCompletion方法唤醒等待的线程
                                wait(0L);
                            } catch (InterruptedException ie) {
                                interrupted = true;
                            }
                        }
                        else
                            //任务已执行完成,则唤醒所有等待的线程
                            notifyAll();
                    }
                }
            } while ((s = status) >= 0);
            if (interrupted)
                //等待时被中断,将当前线程标记为已中断
                Thread.currentThread().interrupt();
        }
        return s;
    }

4.5.invoke / quietlyInvoke

//处理单个任务
public final V invoke() {
        int s;      //先尝试执行任务
        if ((s = doInvoke() & DONE_MASK) != NORMAL) //doInvoke方法的结果status只保留完成态位表示非NORMAL,则报告异常
            reportException(s);     //正常完成,返回原始结果.
        return getRawResult();
    }

//ForkJoinPool::awaitJoin,在该方法中使用循环的方式进行internalWait,满足了每次按截止时间或周期进行等待,同时也顺便解决了虚假唤醒
private int doInvoke() {
        int s; Thread t; ForkJoinWorkerThread wt;
        ///先尝试本线程执行,并获取任务状态,,状态<0表示正常完成,不成功才走后续流程
        return (s = doExec()) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
              //与 doJoin() 方法逻辑基本相同,但在当前线程是ForkJoinWorkerThread时不尝试将该task移除栈并执行,而是等
            (wt = (ForkJoinWorkerThread)t).pool.
            awaitJoin(wt.workQueue, this, 0L) :
            externalAwaitDone();
    }

测试用例如下:

@Test
    public void test3() throws Exception {
        Thread thread=Thread.currentThread();
        ForkJoinTask task=new ForkJoinTask() {
            @Override
            public Object getRawResult() {
                return null;
            }
 
            @Override
            protected void setRawResult(Object value) {
 
            }
 
            @Override
            protected boolean exec() {
                System.out.println(Thread.currentThread().getName()+" start run");
                if(thread==Thread.currentThread()){
                    return false;
                }else{
                    try {
                        Thread.sleep(2000);
                        System.out.println(Thread.currentThread().getName()+" exit");
                        return true;
                    } catch (InterruptedException e) {
                        return false;
                    }
                }
            }
        };
        ForkJoinPool.commonPool().submit(task);
        //阻塞当前线程,等待任务执行完成
        task.invoke();
        System.out.println("main thread exit");
    }

【Java多线程】JUC之线程池(四)ForkJoin_第15张图片

在这里插入图片描述
第二种会无期限阻塞,第一种是正常退出,为啥会有这种情形了?

  • main线程执行invoke方法,invoke方法调用doExec方法返回值等于0后就执行externalAwaitDone方法了,如果执行ForkJoinPool.common.tryExternalUnpush方法返回true,则再次执行doExec方法,因为返回值还是0,则通过wait方法等待了,因为没有其他线程唤醒该线程,就会无期限等待;
  • 如果执行ForkJoinPool.common.tryExternalUnpush方法返回false,说明某个Worker线程已经将该任务从任务队列中移走了,Worker线程会负责执行该任务并修改任务执行状态,如果Worker线程正在执行的过程中则wait等待Worker线程执行完成,Worker执行完成会唤醒等待的main线程,main线程判断任务已完成就正常退出了。

4.6.complete / quietlyComplete/ completeExceptionally

这三个方法都是任务执行完成时调用的,其中

  • complete()方法用于保存任务执行的结果并修改状态,
  • quietlyComplete()方法只修改状态
  • completeExceptionally()用于任务执行异常时保存异常信息并修改状态

其中只有quietlyComplete方法有调用方,都是CountedCompleter及其子类,其调用链如下:
【Java多线程】JUC之线程池(四)ForkJoin_第16张图片
这三个方法的实现如下:

//保存任务执行的结果并修改任务状态
public void complete(V value) {
        try {
            //保存结果
            setRawResult(value);
        } catch (Throwable rex) {
            //出现异常,保存关联的异常
            setExceptionalCompletion(rex);
            return;
        }
        //修改状态,正常完成
        setCompletion(NORMAL);
    }
 
 
public final void quietlyComplete() {
        //修改状态正常完成
        setCompletion(NORMAL);
    }
 
//任务异常结束时,保存异常信息并修改状态
public void completeExceptionally(Throwable ex) {
        //记录异常信息并更新任务状态
        setExceptionalCompletion((ex instanceof RuntimeException) ||
                                 (ex instanceof Error) ? ex :
                                 new RuntimeException(ex)); //如果不是RuntimeException或者Error,则将其用RuntimeException包装一层
    }
    
 //标记当前task的completion状态,同时根据情况唤醒等待该task的线程.
 private int setCompletion(int completion) {
   		//开启一个循环,如果当前task的status已经是各种完成(小于0),则直接返回status,这个status可能是某一次循环前被其他线程完成.
        for (int s;;) {
            if ((s = status) < 0) //如果已完成,直接返回
                return s;
                 //尝试将原来的status设置为它与completion按位或的结果.
            if (U.compareAndSwapInt(this, STATUS, s, s | completion)) {
                //cas修改状态成功
                if ((s >>> 16) != 0) //如果status中有SIGNAL标识,即有线程在等待当前任务执行完成
                      //此处体现了SIGNAL的标记作用,很明显,只要task完成(包含取消或异常),或completion传入的值不小于1<<16,
                		//就可以起到唤醒其他线程的作用.
                    synchronized (this) { notifyAll(); } //唤醒等待的线程
                 //cas成功,返回参数中的completion.
                return completion;
            }
        }
    }
 
 private int setExceptionalCompletion(Throwable ex) {
        //记录异常信息并更新任务状态
        int s = recordExceptionalCompletion(ex);
        if ((s & DONE_MASK) == EXCEPTIONAL) //如果状态是异常完成,则执行钩子方法
            internalPropagateException(ex); //默认是空实现
        return s;
    }
    
  //internalPropagateException方法是一个空方法,留给子类实现,可用于completer之间的异常传递
  void internalPropagateException(Throwable ex) {
    }

4.7. recordExceptionalCompletion / clearExceptionalCompletion / getException

这几个方法都是异常处理的,

  • recordExceptionalCompletion() 用于记录异常信息并修改任务状态
  • getException() 获取当前任务关联的异常信息,如果任务是正常结束的则返回null,如果是被取消则返回CancellationException,如果异常结束则返回执行任务过程中抛出的异常,
  • clearExceptionalCompletion() 是reinitialize调用的,用于清理掉当前任务关联的异常信息
//保存异常信息,并设置状态异常结束
final int recordExceptionalCompletion(Throwable ex) {
        int s;
        if ((s = status) >= 0) {
            //获取hash值
            int h = System.identityHashCode(this);
            final ReentrantLock lock = exceptionTableLock;
            lock.lock(); //加锁
            try {
                //清理掉已经被GC回收掉的ExceptionNode
                expungeStaleExceptions();
                ExceptionNode[] t = exceptionTable;//exceptionTable是一个全局的静态常量
                //计算该节点的索引 ,用hash值和数组长度进行与运算求一个初始的索引
                int i = h & (t.length - 1);
                for (ExceptionNode e = t[i]; ; e = e.next) {
                    if (e == null) {
                        //如果t[i]为null或者遍历完了没有找到匹配的,就创建一个新的ExceptionNode,插入到t[i]链表的前面,保存this,异常对象并退出循环
                        t[i] = new ExceptionNode(this, ex, t[i]);
                        break;
                    }
                    //找到目标节点
                    if (e.get() == this)  //已设置在相同的索引位置的链表中,退出循环.//2
                        break;
                }
              //否则e指向t[i]的next,进入下个循环,直到发现判断包装this这个ForkJoinTask的ExceptionNode已经出现在t[i]这个链表并break(2),
            //或者直到e是null,意味着t[i]出发开始的链表并无包装this的ExceptionNode,则将构建一个新的ExceptionNode并置换t[i],
            //将原t[i]置为它的next(1).整个遍历判断和置换过程处在锁中进行.
            } finally {
                lock.unlock();
            }
         	 //记录成功,将当前task设置为异常完成.
            s = setCompletion(EXCEPTIONAL);
        }
        return s;
    }
 
public final Throwable getException() {
        int s = status & DONE_MASK; 
        return ((s >= NORMAL)    ? null : //如果是正常完成,则返回null
                (s == CANCELLED) ? new CancellationException() : //任务被取消,则返回CancellationException
                getThrowableException()); //任务异常结束,获取之前异常结束时保存的异常信息
    }
 
//将exceptionTableRefQueue中已经被GC回收掉的节点从exceptionTable中移除
private static void expungeStaleExceptions() {
         //poll方法移除并返回链表头,链表中的节点是已经被回收掉了
        for (Object x; (x = exceptionTableRefQueue.poll()) != null;) {
            if (x instanceof ExceptionNode) {
                int hashCode = ((ExceptionNode)x).hashCode;
                ExceptionNode[] t = exceptionTable;
                //计算该节点的索引
                int i = hashCode & (t.length - 1);
                ExceptionNode e = t[i];
                ExceptionNode pred = null;
                while (e != null) {
                    ExceptionNode next = e.next;
                    if (e == x) { //找到目标节点
                        if (pred == null) //x就是链表第一个节点
                            t[i] = next;
                        else  //x是链表中某个节点
                            pred.next = next;
                        break;
                    }
                    //遍历下一个节点
                    pred = e;
                    e = next;
                }
            }
        }
    }
 
 private Throwable getThrowableException() {
        if ((status & DONE_MASK) != EXCEPTIONAL) //不是异常结束,返回null
            return null;
        int h = System.identityHashCode(this);
        ExceptionNode e;
        //加锁
        final ReentrantLock lock = exceptionTableLock;
        lock.lock();
        try {
            //清理掉已经被GC回收掉的ExceptionNode
            expungeStaleExceptions();
            ExceptionNode[] t = exceptionTable;
            //计算所属的数组元素
            e = t[h & (t.length - 1)];
            //遍历链表,e.get()方法返回该ExceptionNode关联的Task
            while (e != null && e.get() != this)
                e = e.next;
        } finally {
            lock.unlock();
        }
        Throwable ex;
        //没有找到当前Task 或者ex为null
        if (e == null || (ex = e.ex) == null) 
            return null;
        if (e.thrower != Thread.currentThread().getId()) {
            //如果保存异常信息的线程不是当前线程,创建一个同类型的异常实例包装原来的异常信息,从而提供准确的异常调用链
            Class<? extends Throwable> ec = ex.getClass();
            try {
                Constructor<?> noArgCtor = null;
                //获取构造函数
                Constructor<?>[] cs = ec.getConstructors();// public ctors only
                for (int i = 0; i < cs.length; ++i) {
                    Constructor<?> c = cs[i];
                    //获取构造函数的参数类型
                    Class<?>[] ps = c.getParameterTypes();
                    if (ps.length == 0)
                        noArgCtor = c; //默认的构造函数
                    else if (ps.length == 1 && ps[0] == Throwable.class) {
                        //如果只有一个参数,且参数类型是Throwable,则创建一个新异常实例
                        Throwable wx = (Throwable)c.newInstance(ex);
                        return (wx == null) ? ex : wx;
                    }
                }
                if (noArgCtor != null) {
                    //有默认的无参构造函数,创建一个实例并设置ex
                    Throwable wx = (Throwable)(noArgCtor.newInstance());
                    if (wx != null) {
                        wx.initCause(ex);
                        return wx;
                    }
                }
            } catch (Exception ignore) {
            }
        }
        return ex;
    }
 
//清理掉当前Task关联的ExceptionNode
private void clearExceptionalCompletion() {
        //获取hash值
        int h = System.identityHashCode(this);
        //加锁
        final ReentrantLock lock = exceptionTableLock;
        lock.lock();
        try {
            ExceptionNode[] t = exceptionTable;
            //计算所属的数组元素
            int i = h & (t.length - 1);
            ExceptionNode e = t[i];
            ExceptionNode pred = null;
            //遍历链表
            while (e != null) {
                ExceptionNode next = e.next;
                if (e.get() == this) {
                    if (pred == null) //this是链表第一个节点
                        t[i] = next;
                    else
                        pred.next = next; //this是链表中的一个节点
                    break;
                }
                //遍历下一个节点
                pred = e;
                e = next;
            }
            //清理掉已经被GC回收掉的ExceptionNode
            expungeStaleExceptions();
            status = 0;
        } finally {
            lock.unlock();
        }
    }

4.8. invokeAll

等待多个任务执行完成,其中第一个任务都是由当前线程执行其他任务是提交到线程池执行多个任务时,如果有一个任务执行异常,则会取消剩余未执行的任务

其实现如下:

//处理两个任务
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
    int s1, s2;
    //提交任务t2,交由线程池执行
    t2.fork();
    //执行任务t1并获取直接结果的任务状态
    if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL)
        t1.reportException(s1);
    //获取任务t2的直接结果状态
    if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL)
        t2.reportException(s2);
}

//处理多个任务
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];
        //任务为空则跑NPE异常
        if (t == null) {
            if (ex == null)
                ex = new NullPointerException();
        }
        //非最后一个任务,则推入线程池执行
        else if (i != 0)
            t.fork();
        //最后一个任务直接调用doInvoke()执行    
        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 = t.getException();
        }
    }
    if (ex != null)
        rethrow(ex);
}

4.9. get

get方法是阻塞当前线程并等待任务执行完成,其效果和实现跟join方法基本一致,最大的区别在于 : 如果线程等待的过程中被中断了,get方法会抛出异常InterruptedException,而join方法不会抛出异常

其实现如下:

 public final V get() throws InterruptedException, ExecutionException {
        //如果是ForkJoinWorkerThread执行doJoin() 否则执行externalInterruptibleAwaitDone()
        int s = (Thread.currentThread() instanceof ForkJoinWorkerThread) ?
            doJoin() : externalInterruptibleAwaitDone();
        Throwable ex;
           //异常处理——取消的任务,抛出CancellationException.
        if ((s &= DONE_MASK) == CANCELLED) //任务被取消
             //异常处理——调用getThrowableException获取异常,封进ExecutionException.
            throw new CancellationException();
        if (s == EXCEPTIONAL && (ex = getThrowableException()) != null) //异常终止
            throw new ExecutionException(ex);
        //无异常处理,返回原始结果.
        return getRawResult(); //返回执行的结果
    }
 
public final V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        int s;
        long nanos = unit.toNanos(timeout);
        if (Thread.interrupted()) //被中断抛出异常
            throw new InterruptedException();
        if ((s = status) >= 0 && nanos > 0L) {
            //获取等待的终止时间
            long d = System.nanoTime() + nanos;
            long deadline = (d == 0L) ? 1L : d; // avoid 0
            Thread t = Thread.currentThread();
            if (t instanceof ForkJoinWorkerThread) {
                //如果是ForkJoinWorkerThread,通过awaitJoin方法等待任务执行完成
                ForkJoinWorkerThread wt = (ForkJoinWorkerThread)t;
                s = wt.pool.awaitJoin(wt.workQueue, this, deadline);
            }
            //如果是普通Java线程
            else if ((s = ((this instanceof CountedCompleter) ?
                           //如果是CountedCompleter,则通过externalHelpComplete等待其执行完成
                           ForkJoinPool.common.externalHelpComplete(
                               (CountedCompleter<?>)this, 0) :
                           //如果是普通的ForkJoinTask,尝试将其从任务队列中pop出来并执行    
                           ForkJoinPool.common.tryExternalUnpush(this) ?
                           doExec() : 0)) >= 0) {
                //如果tryExternalUnpush返回false或者doExec方法返回值大于等于0,即任务未执行完成           
                long ns, ms; // measure in nanosecs, but wait in millisecs
                while ((s = status) >= 0 &&  //任务已执行
                       (ns = deadline - System.nanoTime()) > 0L) {  //等待超时
                    if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) > 0L &&
                        U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                        //cas修改状态加上SIGNAL
                        synchronized (this) {
                            if (status >= 0)
                                //阻塞当前线程指定时间,如果被中断则抛出异常
                                wait(ms); // OK to throw InterruptedException
                            else
                                notifyAll();
                        }
                    }
                }
            }
        }
        if (s >= 0) 
            s = status; //再次读取状态
        if ((s &= DONE_MASK) != NORMAL) { //不是正常执行
            Throwable ex;
            if (s == CANCELLED) //被取消
                throw new CancellationException(); 
            if (s != EXCEPTIONAL) //不是异常终止,则是等待超时
                throw new TimeoutException();
            if ((ex = getThrowableException()) != null) //异常终止
                throw new ExecutionException(ex);
        }
        return getRawResult();
    }
 
//逻辑同externalAwaitDone,区别在于如果被中断抛出异常
//externalAwaitDone不会抛出异常,如果被中断了会将当前线程标记为已中断
private int externalInterruptibleAwaitDone() throws InterruptedException {
        int s;
        if (Thread.interrupted())
            throw new InterruptedException(); //被中断则抛出异常
        if ((s = status) >= 0 &&
            (s = ((this instanceof CountedCompleter) ?
                  ForkJoinPool.common.externalHelpComplete(
                      (CountedCompleter<?>)this, 0) :
                  ForkJoinPool.common.tryExternalUnpush(this) ? doExec() :
                  0)) >= 0) {
            while ((s = status) >= 0) {
                if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                    synchronized (this) {
                        if (status >= 0)
                            wait(0L);
                        else
                            notifyAll();
                    }
                }
            }
        }
        return s;
    }
 

4.10. tryUnfork / reinitialize

//尝试将当前任务从任务队列中pop出来,然后可以在当前线程执行
public boolean tryUnfork() {
        Thread t;
        return (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
                ((ForkJoinWorkerThread)t).workQueue.tryUnpush(this) : //如果Worker线程,尝试将当前任务从其关联的WorkQueue中pop出来
                ForkJoinPool.common.tryExternalUnpush(this)); //非Worker线程,尝试将当前任务从probe属性关联的WorkQueue中pop出来
    }
 
//将任务恢复至初始状态,然后可正常执行
public void reinitialize() {
        if ((status & DONE_MASK) == EXCEPTIONAL)
            clearExceptionalCompletion(); //如果是异常结束,则清除关联的异常信息
        else
            //状态恢复成0
            status = 0;
    }

4.11. FutureJoinTask任务的异常处理

ForkJoinTask在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以ForkJoinTask提供了isCompletedAbnormally()方法检查任务是否已经抛出异常或已经被取消了,并且可以通过ForkJoinTask的getException()方法获取异常。

if(task.isCompletedAbnormally()){
    System.out.println(task.getException());
}
public class ForkJoinDemo {
    public static void main(String[] args) {
        AccumulationTask task = new AccumulationTask(0, 1000);
        if (task.isCompletedAbnormally()) {
            System.out.println(task.getException());
        }
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        Future<Integer> future = forkJoinPool.submit(task);
        try {
            long start = System.currentTimeMillis();
            int r = future.get();
            System.out.println("执行‘0+1+2+3+...+1000'计算用时" + (System.currentTimeMillis() - start) + "毫秒,其结果是:" + r);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    static class AccumulationTask extends RecursiveTask<Integer> {
        private static final int FORK_THRESHOLD = 100;
        private static final long serialVersionUID = 6539052091996442309L;
        private final int start;
        private final int end;

        public AccumulationTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            boolean isContinueFork = (end - start) > FORK_THRESHOLD;
            int sum = 0;
            if (isContinueFork) {   //大于阀值进需要继续分割任务
                //二分法,分成左右两段
                int m = (start + end) / 2;
                AccumulationTask leftTask = new AccumulationTask(start, m);
                AccumulationTask rightTask = new AccumulationTask(m + 1, end);

                //fork方法:分别执行左右两段任务(若任务不够小,将递归调用compute)
                leftTask.fork();
                rightTask.fork();

                //等待左右两段任务执行完,再获取子任务的结果
                int leftResult = leftTask.join();
                int rightResult = rightTask.join();
                sum = leftResult + rightResult;
                return sum;
            }

            //任务足够小了,可以直接计算
            for (int i = this.start; i <= this.end; i++) {
                sum += i;
                if (i == 999) {
                    throw new IllegalStateException();
                }
            }
            return sum;
        }
    }
}

4.12. 获取任务状态、结果的方法

方法名 说明
cancel 取消任务
isDone 判断任务是否正常完成
isCancelled 判断任务是否已取消
isCompletedAbnormally 判断任务是否非正常完成,如被取消或任务执行异常等
isCompletedNormally 判断任务是否执行正常完成,及任务状态是否为NORMAL
getException 获取任务执行的异常结果
completeExceptionally 将任务状态设置为异常,并设置异常结果
complete 将任务设置正常结束,并设置任务执行结果
get 获取任务执行结果,若任务取消或异常,则抛出异常;否则返回任务执行结果

4.12. 总结

整个过程对应分为任务拆分和结果合并,分别对应了ForkJoinTask的fork()方法【异步执行子任务】和join()方法【阻塞获取子线程的结果】。

  • Java1.8中提供的Stream API 都是以ForkJoinPool为基础的,并且共用一个线程池。 默认的线程数为CPU的核心数。
  • 如果任务都是CPU密集型则完成没有问题, 但是如果有IO密集型则很可能由于一个很慢的I/O 拖垮整个系统的性能。所以最好不同类型的ForkJoinPool 执行不同类型的任务计算。

五.大总结

  • ForkJoinPool 可以说是对 ThreadPoolExecutor 的一种增强实现,内部基于“工作窃取”算法实现,二者适用的场景不同。
  • ForkJoinPool 提供了其独特的线程工作队列绑定方式工作分离以及窃取方式
  • ForkJoinPool + ForkJoinTask 配合实现了 Fork/Join 框架
  • ForkJoinTask有两个核心方法——fork()和join(),有三个重要子类——RecursiveAction、RecursiveTask和CountedCompleter
  • ForkJoinPool 的每个工作线程ForkJoinWorkerThread有自己的工作队列WorkQueue ,它是一个双端队列,自己从队列 “头部 ”取任务ForkJoinTask,其它线程从 “尾部” 窃取任务ForkJoinTask
  • ForkJoinPool 最适合的是计算密集型(CPU密集型)的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时,最好配合使用 ManagedBlocker

ForkJoin框架之ForkJoinTask

你可能感兴趣的:(Java多线程,ForkJoin,分治法,工作窃取,线程池,并发)