ForkJoin框架使用和原理剖析

相信大家此前或多或少有了解到ForkJoin框架,ForkJoin框架其实就是一个线程池ExecutorService的实现,通过工作窃取(work-stealing)算法,获取其他线程中未完成的任务来执行。可以充分利用机器的多处理器优势,利用空闲的线程去并行快速完成一个可拆分为小任务的大任务,类似于分治算法。ForkJoin的目标,就是利用所有可用的处理能力来提高程序的响应和性能。本文将介绍ForkJoin框架,依次介绍基础特性、案例使用、源码剖析和实现亮点。

基础特性

ForkJoin框架的核心是ForkJoinPool类,基于AbstractExecutorService扩展。ForkJoinPool中维护了一个队列数组WorkQueue[],每个WorkQueue维护一个ForkJoinTask数组和当前工作线程。ForkJoinPool实现了工作窃取(work-stealing)算法并执行ForkJoinTask。

ForkJoinTask是能够在ForkJoinPool中执行的任务抽象类,父类是Future,具体实现类有很多,这里主要关注RecursiveAction和RecursiveTask。RecursiveAction是没有返回结果的任务,RecursiveTask是需要返回结果的任务。只需要实现其compute()方法,在compute()中做最小任务控制,任务分解(fork)和结果合并(join)。

ForkJoinPool中执行的默认线程是ForkJoinWorkerThread,由默认工厂产生,可以自己重写要实现的工作线程。同时会将ForkJoinPool引用放在每个工作线程中,供工作窃取时使用。

变量说明

ForkJoin框架中,深入理解上面的3个类就可以基本对框架有更好的认识。下面先来大致介绍一下每个类中我们需要关注的字段和相关联系。

ForkJoinPool类

ADD_WORKER: 100000000000000000000000000000000000000000000000 -> 1000 0000 0000 0000,用来配合ctl在控制线程数量时使用

ctl: 控制ForkJoinPool创建线程数量,(ctl & ADD_WORKER) != 0L 时创建线程,也就是当ctl的第16位不为0时,可以继续创建线程

defaultForkJoinWorkerThreadFactory: 默认线程工厂,默认实现是DefaultForkJoinWorkerThreadFactory

runState: 全局锁控制,全局运行状态

workQueues: 工作队列数组WorkQueue[]

config: 记录并行数量和ForkJoinPool的模式(异步或同步)

ForkJoinTask类

status: 任务的状态,对其他工作线程和pool可见,运行正常则status为负数,异常情况为正数

WorkQueue类

qlock: 并发控制,put任务时的锁控制

array: 任务数组ForkJoinTask<?>[]

pool: ForkJoinPool,所有线程和WorkQueue共享,用于工作窃取、任务状态和工作状态同步

base: array数组中取任务的下标

top: array数组中放置任务的下标

owner: 所属线程,ForkJoin框架中,只有一个WorkQueue是没有owner的,其他的均有具体线程owner

ForkJoinWorkerThread类

pool: ForkJoinPool,所有线程和WorkQueue共享,用于工作窃取、任务状态和工作状态同步

workQueue: 当前线程的任务队列,与WorkQueue的owner呼应

ForkJoin-Basic-info
上面各个类及变量关系大致如图所示:ForkJoinPool作为最核心的组件,维护了所有的任务队列WorkQueues,workQueues维护着所有线程池的工作线程,工作窃取算法就是在这里进行的。每一个WorkQueue对象中使用pool保留对ForkJoinPool的引用,用来获取其WorkQueues来窃取其他工作线程的任务来执行。同时WorkQueue对象中的owner是ForkJoinWorkerThread工作线程,绑定ForkJoinWorkerThread和WorkQueue的一对一关系,每个工作线程会优先完成自己队列的任务,当自己队列中的任务为空时,才会通过工作窃取算法从其他任务队列中获取任务。WorkQueue中的ForkJoinTask<?>[] array,是每一个具体的任务,插入array中的第一个任务是最大的任务。

案例使用

到这一步就大概对其组成和工作窃取的方式有了大致的了解,但是关于常用的fork()方法和join()方法,还是一脸迷茫。下面就拿出一个案例示例代码,带大家去深入理解。

这里使用网红ForkJoin案例,1-100数字求和,提升求和效率。

    public class CountRecursiveTask extends RecursiveTask<Integer> {
   
    	  //达到子任务直接计算的阈值
        private int Th = 15;

        private int start;
        private int end;

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

        @Override
        protected Integer compute() {
   
            if (this.end - this.start < Th) {
   
            		//如果小于阈值,直接调用最小任务的计算方法
                return count();
            } else {
   
                //fork 2 tasks:Th = 15
                //如果仍大于阈值,则继续拆分为2个子任务,分别调用fork方法。
                //这里可以根据情况拆成n个子任务
                int middle = (end + start) / 2;
                CountRecursiveTask left = new CountRecursiveTask(start, middle);
                System.out.println("start:" + start + ";middle:" + middle + ";end:" + end
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值