ForkJoin 框架 源码解析

ForkJoin 框架

work-stealing框架规则

框架规则:
I.每一个工作线程都维护自己调度队列中的可运行任务。
II.调度队列支持队列和栈二项性,既支持LIFO(后进先出,栈模式),又支持FIFO(先进先出, 队列模式)。
III.工作线程处理任务时,fork产生的子任务,将会放入到自己的调度队列中。
IV.工作线程使用LIFO(栈模式),来处理自身调度队列中的任务。
V.当某个工作线程的调度队列无任务时,随机一个别的调度队列,通过FIFO模式(先进先出, 队列模式), 取出(窃取)一个任务去运行。
VI.当一个工作线程调用了join操作,被join的目标任务如果没有完成,线程可能偷取其他任务进行处理,直到目标任务已经结束(通过isDone方法)。所有的任务都会无阻塞的完成。
VII.当一个工作线程本地队列没有任务处理,同时也无法从线程池中的其他队列中偷取到任务时,它就会退出扫描(通过yield、sleep和/或者优先级调整)一段时间,超时后再度尝试,除非所有的工作线程都处于空闲的状态。在这种情况下,他们都会阻塞直到新的任务加入。

补充:每个工作线程都会有自己的调度队列,但调度队列也可能没有工作线程

基本算法

protected long compute() {
   
         if (end - start <= THRESHOLD) {
   
             return doResult();
         } else {
   
             high.fork();
             low.fork();
             return low.join() + high.join();
        }
}

框架说明

Forkjoin框架主要是采用分治思想来做处理,把大任务切分成若干小任务,然后小任务再切成更小的任务,以此递归,直到切到最小单元处理任务为止。任务处理上线程采用work-stealing规则进行处理。简单的任务切分模式如下图所示:
在这里插入图片描述

框架组成

I. ForkJoinPool: 线程池类,管理任务队列,队列数组中,奇数索引位是带有工作线程的任务队列,偶数索引位(64个槽位)是不带工作线程的共享队列。提供了任务提交入口,和任务偷取框架核心运行支持。
II. ForkJoinPool.WorkQueue: 任务环形双端队列,实现了队列和栈的二项性。队列采用cas原子操作实现多线程安全。
III.ForkJoinWorkThread: 工作线程,实现了向线程池奇数索引注册工作队列。
IV. ForkJoinTask: 任务抽象类,提供了fork和join操作接口,子类有三个,分别是:
1.RecursiveAction,递归无返回结果的任务, compute拆分子任务
2.RecursiveTask, 递归有返回结果的任务,compute拆分子任务
3.CountedCompleter, 一个特殊的计数任务类,类中维护着子任务计数器pending和父任务指针,这样就形成了,从根任务到子任务一个树形结构,每个结点都有孩子的计数器pending,一个任务完成后会调用完成接口, 如果子任务数pending递减为0,会回调任务完成接口和再递归调用父任务的pending递减操作,然后再判断父任务的pending是否递减到0…,就这样递归调用,直到根任务pending递减到0,则此根任务下的所有任务完成。此类任务,ForkJoin框架中作了特殊处理,任务的运行偷取都做了相对的特殊处理,因此在运行性能上会更高。

主要常量和成员变量

 // Bounds
    /* 如果并发线程数是MAX_CAP=0x7fff, 则队列数组的最大长度是0x10000
    *,所提供的偶数索引槽数:0x8000, 奇数索引槽数:0x8000, 所有的最大非工作线程的队列有槽位:64 < 0x8000,
    *    最大并发工作线程队列所在奇数槽位:0x7fff < 0x8000, 
           *所以队列数组不会超过SMASK = 0xffff*/
    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个偶数槽位,用来非工作线程队列的偶数索引位
    static final int SQMASK       = 0x007e;        // max 64 (even) slots

    // Masks and units for WorkQueue.scanState and ctl sp subfield
    //任务被运行处理的时候,会标记此状态,表示任务正在扫描
    static final int SCANNING     = 1;             // false when running tasks
    //任务处于空闲状态,未被激活
    static final int INACTIVE     = 1 << 31;       // must be negative
    //ctl 低16位SS(栈顶队列池化索引)的版本号(防止CAS的ABA问题)
    static final int SS_SEQ       = 1 << 16;       // version count

    // Mode bits for ForkJoinPool.config and WorkQueue.config
    //int低16位掩码
    static final int MODE_MASK    = 0xffff << 16;  // top half of int
    //后进先出,栈模式
    static final int LIFO_QUEUE   = 0;
    //先进先出,队列模式
    static final int FIFO_QUEUE   = 1 << 16;
    //共享队列模式
static final int SHARED_QUEUE = 1 << 31;       // must be negative


//ID SS, ctl 低32位掩码
    private static final long SP_MASK    = 0xffffffffL;
    //UC, 高32位掩码
    private static final long UC_MASK    = ~SP_MASK;

    // Active counts
    //活动的工作线程移位操作
    private static final int  AC_SHIFT   = 48;
    //活跃的线程 高16位+1增量单位
    private static final long AC_UNIT    = 0x0001L << AC_SHIFT;
    //活跃线程AC,高16位掩码
    private static final long AC_MASK    = 0xffffL << AC_SHIFT;

    // Total counts
    //总的工作线程TC,移位
    private static final int  TC_SHIFT   = 32;
    //TC增量单位+1,高(32-48)位
    private static final long TC_UNIT    = 0x0001L << TC_SHIFT;
    //tc, 32-48掩码
    private static final long TC_MASK    = 0xffffL << TC_SHIFT;
    //增加工作线程的位运算,最高位1,如果AC最高位1,则为负数未达到并发线程量,否则为0,则达到最大并发量
    private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15);

    // runState bits: SHUTDOWN must be negative, others arbitrary powers of two
    //lock
    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
    //池控制,AC (48-64), TC(32-48), SS(16-32), ID(0-16)
volatile long ctl;                   // main pool control
//线程池运行状态
volatile int runState;               // lockable status
//配置,并行度和队列模式进行逻辑或运算组合
final int config;                    // parallelism, mode
//随机种子
int indexSeed;                       // to generate worker index
//线程池的工作队列数组,奇数索引放有工作线程的队列,偶数索引(64槽位)共享工作队列
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

ctl 每个16位字段意义:

ac 高位(48-64) tc 高位(32-48) ss 低位(16-32) id 低位(0-16)
0000000000000000 0000000000000000 0000000000000000 0000000000000000

ctl池控制长整形表示,总共有64位,从低位到高位每16位为一个字段,共分成4个字段,这个4个字段的意义如下:
AC: 活动线程数量,线程池构造初始化是负的最大并行度。当有新的工作线程加入或者激活时,就会 +1,直到ac加到0时,激活线程达到最大并行度了。设计成负数的好处是判断ctl是负值时,就说明活动线程ac没有达到饱和,还可以继续增加或者激活工作线程。
TC:总工作线程数量,线程池构造初始化是负的最大并行度。当有新的工作线程加入或者激活时,就会 +1,直到tc加到0时,总线程数达到最大并行度了。一般情况下ac值小于等于tc.因为在没有任务处理的情况下,可能总工作线程不变,激活的线程会变成空闲线程,这时ac值就会变小了。
SS:栈顶等待线程(最后一个处于空闲状态的等待线程)的版本号和状态,版本的控制是16-31位,每次版本变更做+1,栈顶线程状态是第32位来控制,当第32位为1时,表示空闲,0时表示激活。
ID: 栈顶等待线程在线程池中的工作队列数组索引。

ForkJoinPool说明

1.工作线程任务队列:此类型队列拥有一个工作线程,此类型队列任务只能是自身工作线程的fork任务。
2. 共享任务队列:此类型队列没有工作线程,此类型的队列任务是由外部程序提交的任务。
3. 线程池任务队列数组索引规则:偶数位是共享任务队列,共享任务队列只有64个偶数索引槽位。奇数索引是工作线程任务队列。
4. 任务扫描(偷取FIFO模式): 工作线程会生成一个随机开始索引,从池队列数组中开始扫描任务队列(包括自己的队列),a. 扫描到有效的任务,则开始处理任务,任务处理完成后,则开始处理自己内部队列的中的全部任务(LIFO),内部队列任务处理完成后。重新继续扫描任务。b. 未扫描到有效任务,则工作线程设置成空闲状态,并进行等待,如果等待成功,则进行park, 有新任务进入后,会被unpark唤醒,否则工作线程会进入销毁流程。
5. 任务提交:外部任务提交时会push到偶数索引位的共享队列中,任务处理过程中有compute产生的子任务,则子任务会被fork到当前工作线程任务队列中。
6.计数任务处理:这是一个特殊的任务。线程池有针对此任务会做特殊处理。任务awaitJoin时对计数任务调用帮助完成任务时,会帮助完成任务结点下的所有子任务。
7. 帮助偷取处理任务:任务在doJoin时,可能会调到awaitJoin, awaitJoin中,任务如果是 a.计算任务则会调用帮助完成任务(参考6). b.普通任务, 则会调用帮助偷取任务处理,以免当前工作线程空转等待任务完成,这样担任工作线程的利用率,提升了任务并发处理效率。

框架示意图:
在这里插入图片描述

源码说明

外部提交任务externalPush

//外部提交任务,此任务会提交到64个偶数槽中的任务队列其中一个,线程探针是通过提交线程生成的,
//通过线程探针生成队列数组索引,如果该索引下有队列则push到队列中,否则调用externalSubmit(), 即走完队列初始化全部流程加入到任务队列中

  final void externalPush(ForkJoinTask<?> task) {
   
        WorkQueue[] ws; WorkQueue q; int m;
        //获取当前线程的探针
        int r = ThreadLocalRandom.getProbe();
        //线程池运行状态
        int rs = runState;
        //线程池队列数组有效
        if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
        	//根据r,获取SQMASK 掩码64个槽位中的r位索引,判断索引位队列是否有效
            (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 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)) {
   
            	//top偏移位
                int j = ((am & s) << ASHIFT) + ABASE;
                //放入任务到队列中
                U.putOrderedObject(a, j, task);
                //top指针加1
                U.putOrderedInt(q, QTOP, s + 1);
                //unlock
                U.putIntVolatile(q, QLOCK, 0);
                //任务数=1,如果正好任务被某个工作线程偷走,其他线程扫描时没扫描到处于空闲等待状态,
                //<1可能其他线程都在空闲等待中,所以这时通知空闲线程有任务要处理了
                if (n <= 1)
                    signalWork(ws, q);
                return;
            }
            //unlock
            U.compareAndSwapInt(q, QLOCK, 1, 0);
        }
        externalSubmit(task);
}

外部提交任务externalSubmit

//外部任务提交初始化一套流程,初始线程池任务队列数组,初始化新的偶数槽位(最大64个槽位)队列
    //初始化完成后,如果提交任务成功,则通知工作线程处理任务。
    private void externalSubmit(ForkJoinTask<?> task) {
   
        int r;                                    // initialize caller's probe
        //调用者线程探针初始化,然后再取随机探针
        if ((r = ThreadLocalRandom.getProbe()) == 0) {
   
            ThreadLocalRandom.localInit();
            r = ThreadLocalRandom.getProbe();
        }
        for (;;) {
   
            WorkQueue[] ws; WorkQueue q; int rs, m, k;
            boolean move = false;
            //线程池被终止了(SHUTDOWN),  帮助关闭
            if ((rs = runState) < 0) {
   
                tryTerminate(false, false);     // help terminate
                throw new RejectedExecutionException();
            }
            //如果运行状态未开始,或者池化队列数组未初始化,则进行初始化
            else if ((rs & STARTED) == 0 ||     // initialize
                     ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
   
                int ns = 0;
                //lock
                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
                        //并行数超出1,取 -1,否则取1
                        int n = (p > 1) ? p - 1 : 1;
                        //保证2的幂次方,这样就可以通过位运行取模
                        n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
                        n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
                        //new 队列数组
                        workQueues = new WorkQueue[n];
                        ns = STARTED;
                    }
                } finally {
   
                	//unlock 并加上运行初始化状态
                    unlockRunState(rs, (rs & ~RSLOCK) | ns);
                }
            }
            //和SQMASK逻辑运算后,结果会变成偶数,只有64个糟位, 根据探针r取偶数索引位的队列
            else if ((q = ws[k = r & m & SQMASK]) != null) {
   
            	//队列lock
                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
                    	//队列任务数未满
                        if ((a != null && a.length > s + 1 - q.base) ||
                        		//或者队列扩容成功
                            (a = q.growArray()) != null) {
   
                        	//top在数组中的偏移量
                            int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
                            //放入队列
                            U.putOrderedObject(a, j, task);
                            //top + 1
                            U.putOrderedInt(q, QTOP, s + 1);
                            submitted = true;
                        }
                    } finally {
   
                    	//unlock
                        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;
                //lock
                rs = lockRunState();           // publish index
                if (rs > 0 &&  (ws = workQueues) != null &&
                    k < ws.length && ws[k] == null)
                	//队列放入线程池队列数组中
                    ws[k] = q;                 // else terminated
                //unlock
                unlockRunState(rs, rs & ~RSLOCK);
            }
            else
                move = true;                   // move if busy
            if (move)
                r = ThreadLocalRandom.advanceProbe(r);
        }
    }

通知工作线程处理任务signalWork

//通知工作线程处理任务,如果工作线程未达到并行度,则创建新的工作线程。
//否则查看ctl栈顶处于空闲状态的工作线程,如果有,则唤醒

  final void signalWork(WorkQueue[] ws, WorkQueue q) {
   
        long c; int sp, i; WorkQueue v; Thread p;
        //激活的线程量未达到总并发量
        while ((c = ctl) < 0L) {
                          // too few active
        	//ctl栈顶未有waiters
            if ((sp = (int)c) == 0) {
                     // no idle workers
            	//ctl 最高位1,<0,未达到并发量
                if ((c & ADD_WORKER) != 0L)            // too few workers
                	//增加工作线程
                    tryAddWorker(c);
                break;
            }
            //工作线程数组为空,退出
            if (ws == null)                            // unstarted/terminated
                break;
            //ctl栈顶waiter索引,超出了工作线程数组长度,?
            if (ws.length <= (i = sp & SMASK))         // terminated
                break;
            //此索引的工作线程被撤销停止了
            if ((v = ws[i]) == null)                   // terminating
                break;
            //增加ctl栈顶工作线程的版本号,并设置激活状态
            int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
            //ctl栈顶状态和工作线程状态比较
            int d = sp - v.scanState;                  // screen CAS
            //激活线程数+1,栈顶状态工作线程以前的状态
            long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
            //原子更新ctl, 如果更新不成功说明被其他线程激活了或者阻塞超时了
            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;
        }
    }

增加工作线程tryAddWorker

//尝试增加一个工作线程

  private void tryAddWorker(long c) {
   
        boolean add = false;
        do {
   
            long nc = ((AC_MASK & (c + AC_UNIT)) | //激活线程数量+1
                       (TC_MASK & (c + TC_UNIT))); //工作线程总数+1
            //ctl 没有变化
            if (ctl == c) {
   
                int rs, stop;                 // check if terminating
                //加锁,并判断线程池是否停止
                if ((stop = (</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值