JUC之九:ForkJoinPool主流程解析

JUC之九:ForkJoinPool主流程解析

一、前言

ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性。对于那种大任务分割小任务的场景(分治)尤其有用。

二、几个重要的角色

  • ForkJoinPool: 这是线程池的核心技术,也是该类型方法的入口,后续会详细介绍重要的方法流程。
  • ForkJoinTask: 具体的任务抽象类,一般使用 RecursiveTask 以及 RecursiveAction
  • WorkQueue: 内部类,任务队列,有两种模式FIFO、LIFO。
  • WorkQueue[]: ForkJoinPool 中的任务分为两种, 一种是外部提交的 如execute(),submit(),invoke(),一种是内部fork的,内部fork的都在工作队列数组奇数的索引位,外部提交的都在工作队列数组偶数索引位。工作线程都在奇数位的工作队列上。

三、重要属性

3.1、ForkJoinPool的核心参数

  • 首先看一下常量:这些常量后面主要用于二进制的范围运算
    static final int SMASK        = 0xffff;        // 低16位,线程所在的最大索引位
    static final int MAX_CAP      = 0x7fff;        // 工作线程最大容量
    static final int EVENMASK     = 0xfffe;        // 偶数,在计算存放线程队列索引位置时用到
    static final int SQMASK       = 0x007e;        // 偶数,计算外部提交的任务所在工作队列索引

    // Masks and units for WorkQueue.scanState and ctl sp subfield
    static final int SCANNING     = 1;             // 扫描任务状态时试用
    static final int INACTIVE     = 1 << 31;       // 未活跃状态
    static final int SS_SEQ       = 1 << 16;       // 版本号

    // Mode bits for ForkJoinPool.config and WorkQueue.config
    static final int MODE_MASK    = 0xffff << 16;  // int高16位,与config属性确定FIFO或者LIFO
    static final int LIFO_QUEUE   = 0;				
    static final int FIFO_QUEUE   = 1 << 16;
    static final int SHARED_QUEUE = 1 << 31;       // 共享模式

对应的二进制如下所示:

在这里插入图片描述

  • 下面是 CTL的常量计算掩码。
    // Lower and upper word masks
    private static final long SP_MASK    = 0xffffffffL;	//低32位掩码
    private static final long UC_MASK    = ~SP_MASK;	//高32位掩码

    // Active counts 活跃线程的数量,位于高16位 48-64位
    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 总线程数 位于32-48位
    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); // 创建了线程的标记

在这里插入图片描述

CTL着重说一下,该变量贯穿了整个线程池的执行流程。

它是一个64位的二进制,每16位代表一种状态。

AC: 活动的工作线程数量,一般是负值,当等于0时,说明已经达到了最大的线程数。

TC: 总的工作线程数量总数,一般是负值,说明总的工作线程已经饱和,并且,AC小于等于TC)

SS: 栈顶工作线程状态和版本数(每一个线程在挂起时都会持有前一个等待线程所在工作队列的索引,由此构成一个等待的工作线程栈,栈顶是最新等待的线程,第一位表示状态1.不活动 0.活动,后15表示版本号)。

ID: 栈顶工作线程所在工作队列的池索引。

这样设计的好处是,通过观察AC或TC的符号(正负)就可以判断(活动|总)工作线程是否达到并行度。令sp = (int)ctl, sp取ctl的后32位,即SS|ID,如果sp非0,则可知有空闲线程在等待。

  • 线程的状态
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;                   // 线程主要控制属性,每16位控制一个信息
volatile int runState;               // 线程运行状态,也就是上面的值
final int config;                    // 并行度 | 模式(FIFO/LIFO)
int indexSeed;                       // to generate worker index
volatile WorkQueue[] workQueues;     // 工作队列数组
final ForkJoinWorkerThreadFactory factory;
final UncaughtExceptionHandler ueh;  // per-worker UEH
final String workerNamePrefix;       // to create worker name string
volatile AtomicLong stealCounter;    // 用于监视窃取的数量

config属性是一个32位的 ,高16位表示模式,准确的说是第17位,低16位表示并行度。

3.2、WorkQueue内的属性

        static final int INITIAL_QUEUE_CAPACITY = 1 << 13;

        static final int MAXIMUM_QUEUE_CAPACITY = 1 << 26; // 64M

        // Instance fields
        volatile int scanState;    //小于0:不活跃,奇数:扫描中,偶数:活跃运行中
        int stackPred;             // pool stack (ctl) predecessor
        int nsteals;               // 窃取的次数
        int hint;                  // randomization and stealer index hint
        int config;                // 当前队列所在工作队列数组的索引、模式(LIFO/FIFO)
        volatile int qlock;        // 1: locked, < 0: terminate; else 0
        volatile int base;         // index of next slot for poll
        int top;                   // index of next slot for push
        ForkJoinTask<?>[] array;   // 存放具体任务的数组
        final ForkJoinPool pool;   // the containing pool (may be null)
        final ForkJoinWorkerThread owner; // 当前队列是否拥有线程否则为null
        volatile Thread parker;    // 线程是否阻塞,否则为null
        volatile ForkJoinTask<?> currentJoin;  // task being joined in awaitJoin
        volatile ForkJoinTask<?> currentSteal; // mainly used by helpStealer

重点说一下config,是一个32位的,高16位表示模式同上config,低16位表示索引位置。

3.3、ForkJoinTask内的属性

    volatile int status; // 任务执行状态
    static final int DONE_MASK   = 0xf0000000;  // 最终完成状态掩码,任务最终的状态判断条件
    static final int NORMAL      = 0xf0000000;  // 正常完成	负数
    static final int CANCELLED   = 0xc0000000;  // 取消	 负数
    static final int EXCEPTIONAL = 0x80000000;  // 异常	 负数
    static final int SIGNAL      = 0x00010000;  // 需要被唤醒标志 正数
    static final int SMASK       = 0x0000ffff;  // short bits for tags

四、构造方法

有两个对外提供的构造方法一个无参,一个需要自定义一些参数。

public ForkJoinPool(int parallelism) {
        this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
    }

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();
    }
  • int parallelism:并行度
  • ForkJoinWorkerThreadFactory factory:创建线程的工厂类
  • UncaughtExceptionHandler handler:补偿策略
  • boolean asyncMode:FIFO模式还是LIFO模式,默认是LIFO

不过其实可以用公共的,在静态方法内初始化了一个commonPool,就无需自己创建了。线程池默认使用这个,也可自己指定。

doug lea 大佬用了大量的位运算去控制各种状态的转换,理解起来相对比较复杂,真看不懂只看大致流程知道它干什么的就行了不用仔细研究位运算,只知道他是控制状态的就行了。

五、加锁、解锁方法

5.1、lockRunState加锁

    private int lockRunState() {
        int rs;
        return ((((rs = runState) & RSLOCK) != 0 ||
                 !U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK)) ?
                awaitRunStateLock() : rs);
    }
转换为这样可能更好看一点
    if ((rs = runState) & RSLOCK) != 0 || !U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK))){
        return awaitRunStateLock()
    }else{
        return rs
    }

1、(rs = runState) & RSLOCK) != 0 这一部分若为true,说明其他线程持有锁,直接进入awaitRunStateLock方法。
2、(rs = runState) & RSLOCK) != 0 为false,进入下半段,通过cas修改runState的值,修改失败,表示其他线程抢先一步加锁,同样也进入awaitRunStateLock方法。
3、不满足上述两者,直接返回runState的值。

5.2、awaitRunStateLock具体加锁方法

   private int awaitRunStateLock() {
        Object lock;
        boolean wasInterrupted = false;
        for (int spins = SPINS, r = 0, rs, ns;;) {
            1、判断是否加runState & RSLOCK==0说明没有加锁
            if (((rs = runState) & RSLOCK) == 0) {
                if (U.compareAndSwapInt(this, RUNSTATE, rs, ns = rs | RSLOCK)) {
                    if (wasInterrupted) {
                        try {
                            1.1、重置线程中断标记
                            Thread.currentThread().interrupt();
                        } catch (SecurityException ignore) {
                        }
                    }
                    return ns;
                }
            }
            2、下面这两个
            else if (r == 0)
                r = ThreadLocalRandom.nextSecondarySeed();
            else if (spins > 0) {
                r ^= r << 6; r ^= r >>> 21; r ^= r << 7; // xorshift
                if (r >= 0)
                    --spins;
            }
            3、线程池刚开始初始化,当前线程让出cpu资源,让其快速初始化
            else if ((rs & STARTED) == 0 || (lock = stealCounter) == null)
                Thread.yield();   // initialization race
            4、当前线程准备好进入休眠状态了,设置需要被唤醒标志位
            else if (U.compareAndSwapInt(this, RUNSTATE, rs, rs | RSIGNAL)) {
                synchronized (lock) {
                    4.1、不等于0说明设置好了标志位,直接进入阻塞等待
                        反之,说明当前刚设置好了唤醒标志,就被其他线程唤醒了自己就不用等待了,也调用一次唤醒方法
                    if ((runState & RSIGNAL) != 0) {
                        try {
                            lock.wait();
                        } catch (InterruptedException ie) {
                            if (!(Thread.currentThread() instanceof
                                  ForkJoinWorkerThread))
                                wasInterrupted = true;
                        }
                    }
                    else
                        lock.notifyAll();
                }
            }
        }
    }

5.3、unlockRunState解锁方法

    private void unlockRunState(int oldRunState, int newRunState) {
        1、清楚唤醒标志位
        if (!U.compareAndSwapInt(this, RUNSTATE, oldRunState, newRunState)) {
            Object lock = stealCounter;
            runState = newRunState;              // clears RSIGNAL bit
            2、检查其他线程是否开始窃取任务,若开始则有可能处于wait状态,进行唤醒操作。
            if (lock != null)
                synchronized (lock) { lock.notifyAll(); }
        }
    }

加锁解锁说完了,这儿说一下他的位运算

(runState & 状态常量),若等于0说明还不是该状态,如runState & RSLOCK == 0 说明还没加锁,以此类推。

再看一下调用unlockRunState方法传入的newRunState的参数的值有以下几种可能:

1、(rs & ~RSLOCK) 意思就是清除锁定状态,(rs & ~RSLOCK & RSLOCK )一定为0。

2、(rs & ~RSLOCK | 状态常量) 清楚锁定状态,设置上该状态常量的状态。

六、具体调用流程解析

1、入口

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

    public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) { // 提交并立刻返回任务,ForkJoinTask实现了Future,支持异步取消等操作
        if (task == null)
            throw new NullPointerException();
        externalPush(task);
        return task;
    }

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

可以发现都调用了externalPush方法,下面进入该方法的解析。

2、externalPush

    final void externalPush(ForkJoinTask<?> task) {
        WorkQueue[] ws; WorkQueue q; int m;
        1、获取一个随机的索引
        int r = ThreadLocalRandom.getProbe();
        2、线程池的状态
        int rs = runState;
        3.1、workQueues已经创建完成
          2、ws[m & r & SQMASK]找到的随机索引位不为null,SQMASK为偶数,所以最终位运算结果为偶数索引
          3、rs>0 线程池开始运行
          4U.compareAndSwapInt(q, QLOCK, 0, 1) cas加锁成功
        if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
            (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
            U.compareAndSwapInt(q, QLOCK, 0, 1)) {
            ForkJoinTask<?>[] a; int am, n, s;
            4、工作队列的线程数组不为null,并且数组还有空闲容量
            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);//将task放在array的索引j处
                U.putOrderedInt(q, QTOP, s + 1);//TOP++
                U.putIntVolatile(q, QLOCK, 0);//解锁
                if (n <= 1)
                    //n<=1,说明至多有一个任务在等待,也可能无任务等待,所以可能有睡眠的线程,尝试去唤醒
                    signalWork(ws, q);
                return;
            }
            5、放入任务失败,也进行解锁
            U.compareAndSwapInt(q, QLOCK, 1, 0);
        }
        6、外部任务的提交进入该接口
        externalSubmit(task);
    }
  • m & r & SQMASK 位运算结果一定为 偶数 ,所以外部的任务会放到偶数索引位。
  • 复杂的逻辑其实最终都会进入到externalSubmit方法,继续解析。

总的来说这是一种捷径,若工作队列已经初始化完成,并且容量足够则直接将任务放到任务栈中,若等待的任务小于一,则尝试唤醒或创建线程。反之进入externalSubmit方法进行解析。

3、externalSubmit

private void externalSubmit(ForkJoinTask<?> task) {
        int r;                                    // initialize caller's probe
    	1、获取一个随机索引
        if ((r = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();
            r = ThreadLocalRandom.getProbe();
        }
    	2、开始自旋,注意几个出口
        for (;;) {
            WorkQueue[] ws; WorkQueue q; int rs, m, k;
            boolean move = false;
            3、runState<0说明线程池已经终止了
            if ((rs = runState) < 0) {
                tryTerminate(false, false);     // help terminate
                throw new RejectedExecutionException();
            }
            4、线程池还未开始初始化,进行初始化
            else if ((rs & STARTED) == 0 ||     // initialize
                     ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
                int ns = 0;
                4.1、加锁
                rs = lockRunState();
                try {
                    if ((rs & STARTED) == 0) {
                        U.compareAndSwapObject(this, STEALCOUNTER, null,
                                               new AtomicLong());
                        // create workQueues array with size a power of two
                        4.2、获取并行度
                        int p = config & SMASK; // ensure at least 2 slots
                        4.3、这儿在hashmap中解析过,就是获取大于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 {
                    4.4、解锁并将线程池的状态设置为运行状态
                    unlockRunState(rs, (rs & ~RSLOCK) | ns);
                }
            }
            5、该偶数位索引工作队列已经初始化
            else if ((q = ws[k = r & m & SQMASK]) != null) {
                5.1、首先进行加锁
                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
                        5.2、growArray会进行初始化或扩容,所以这儿就是判断a存在空间放置任务
                        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);//task放入到索引j处
                            U.putOrderedInt(q, QTOP, s + 1);//top++
                            submitted = true;
                        }
                    } finally {
                        U.compareAndSwapInt(q, QLOCK, 1, 0);
                    }
                    if (submitted) {
                        5.3、前面已经放入了新的任务,唤醒线程或创建线程开始干活了
                        signalWork(ws, q);
                        return;
                    }
                }
                move = true;                   // move on failure
            }
            6、到这说明工作队列数组已经初始化完成,但当前偶数索引位为null,则创建WorkQueue
               未加锁,准备创建工作队列
            else if (((rs = runState) & RSLOCK) == 0) { // create new queue
                q = new WorkQueue(this, null);
                q.hint = r;
                //config 工作队列索引 | 模式
                q.config = k | SHARED_QUEUE;
                //设置激活状态
                q.scanState = INACTIVE;
                //加锁
                rs = lockRunState();           // publish index
                if (rs > 0 &&  (ws = workQueues) != null &&
                    k < ws.length && ws[k] == null)
                    6.1、将工作队列放入到索引k处
                    ws[k] = q;                 // else terminated
                unlockRunState(rs, rs & ~RSLOCK);
            }
            else
                move = true;                   // move if busy
            if (move)
                //换个位置继续自旋
                r = ThreadLocalRandom.advanceProbe(r);
        }
    }
  • 3、线程池已经终止了,抛出异常
  • 4、还未初始化,则先初始化WorkQueue数组,期间需要锁定runstate。
  • 5、WorkQueue数组已经初始化完成,并且当前偶数索引位不为null,则将任务放到该工作队列内,尝试唤醒线程开始工作。
  • 6、WorkQueue数组已经初始化完成,但当前偶数索引位为null,则新建工作队列,设置为未激活状态,期间需要锁定runState

所以第一次进入这执行顺序应该是 4 -->6–>5

4、signalWork

   final void signalWork(WorkQueue[] ws, WorkQueue q) {
        long c; int sp, i; WorkQueue v; Thread p;
        while ((c = ctl) < 0L) {                       // too few active
            1、这儿获取了ctl的低32位,如果为0说明没有空闲的线程
            if ((sp = (int)c) == 0) {                  // SP_MASK
                2、如果c & ADD_WORKER = 0 说明达到了最大线程数,无法创建新的线程
                if ((c & ADD_WORKER) != 0L)            // too few workers
                    tryAddWorker(c);
                break;
            }
            3、还没初始化
            if (ws == null)                            // unstarted/terminated
                break;
            4、数组长度小于等于栈顶线程的索引,这儿好像永远为false(不确定)
            if (ws.length <= (i = sp & SMASK))         // terminated
                break;
            5、正在终止
            if ((v = ws[i]) == null)                   // terminating
                break;
            6、作为下一个scanState待更新的值(增加了版本号,并且调整为激活状态)
            int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
            7、如果d为0,则说明scanState还未更新过,然后才考虑CAS ctl
            int d = sp - v.scanState;                  // screen CAS
            8、AC活跃线程数+1 | 上一个等待的线程的索引
            long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
            if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
                9、激活线程
                v.scanState = vs;                      // activate v
                if ((p = v.parker) != null)
                    U.unpark(p);
                break;
            }
            10、没有任务,直接退出
            if (q != null && q.base == q.top)          // no more work
                break;
        }
    }

这儿有两种执行方法:

  • 创建一个新的工作线程
  • 唤醒休眠的工作线程

5、tryAddWorker

    private void tryAddWorker(long c) {
        boolean add = false;
        do {
            1、AC活跃线程数目加一,总线程数加一
            long nc = ((AC_MASK & (c + AC_UNIT)) |
                       (TC_MASK & (c + TC_UNIT)));
            if (ctl == c) {
                int rs, stop;                 // check if terminating
                2、加锁成功并且不是停止状态
                if ((stop = (rs = lockRunState()) & STOP) == 0)
                    2.1、更新ctl的值
                    add = U.compareAndSwapLong(this, CTL, c, nc);
                unlockRunState(rs, rs & ~RSLOCK);
                if (stop != 0)
                    break;
                if (add) {
                    3、创建线程
                    createWorker();
                    break;
                }
            }
            4、重新获取ctl,线程未达到最大线程数并且没有空闲的线程
        } while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
    }

查看是否满足创建线程的条件,若满足修改ctl的值,开始创建线程。

6、createWorker

    private boolean createWorker() {
        ForkJoinWorkerThreadFactory fac = factory;
        Throwable ex = null;
        ForkJoinWorkerThread wt = null;
        try {
            1、用线程工厂创建一个线程开始执行任务
            if (fac != null && (wt = fac.newThread(this)) != null) {
                wt.start();
                return true;
            }
        } catch (Throwable rex) {
            ex = rex;
        }
        2、创建线程失败,注销更新ctl的值
        deregisterWorker(wt, ex);
        return false;
    }

7、deregisterWorker

final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
        WorkQueue w = null;
    	1、线程不为null,并且该线程持有的workQueue也不为null
        if (wt != null && (w = wt.workQueue) != null) {
            WorkQueue[] ws;                           // remove index from array
            1.1、获取当前workQueue在数组中的索引
            int idx = w.config & SMASK;
            int rs = lockRunState();
            1.2、idx索引处与当前线程持有的workQueue是同一个对象,直接置空
            if ((ws = workQueues) != null && ws.length > idx && ws[idx] == w)
                ws[idx] = null;
            unlockRunState(rs, rs & ~RSLOCK);
        }
        long c;                                       // decrement counts
    	2、AC与TC都减一
        do {} while (!U.compareAndSwapLong
                     (this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) |
                                           (TC_MASK & (c - TC_UNIT)) |
                                           (SP_MASK & c))));
    	3、清空workQueue,将其中的task取消
        if (w != null) {
            w.qlock = -1;                             // ensure set
            w.transferStealCount(this);
            w.cancelAll();                            // cancel remaining tasks
        }
        for (;;) {                                    // possibly replace
            WorkQueue[] ws; int m, sp;
            4、已经终止了跳出循环
            if (tryTerminate(false, false) || w == null || w.array == null ||
                (runState & STOP) != 0 || (ws = workQueues) == null ||
                (m = ws.length - 1) < 0)              // already terminating
                break;
            5、有空闲的线程,进行补偿唤醒空闲的线程
            if ((sp = (int)(c = ctl)) != 0) {         // wake up replacement
                if (tryRelease(c, ws[sp & m], AC_UNIT))
                    break;
            }
            6、线程数量未饱和,新建线程
            else if (ex != null && (c & ADD_WORKER) != 0L) {
                tryAddWorker(c);                      // create replacement
                break;
            }
            else                                      // don't need replacement
                break;
        }
        if (ex == null)                               // help clean on way out
            ForkJoinTask.helpExpungeStaleExceptions();
        else                                          // rethrow
            ForkJoinTask.rethrow(ex);
    } 

该方法其实就是注销线程,清空WorkQueue,更新ctl,最后若满足条件做适当的补偿。

该方法是创建线程失败后的操作,我们继续讲解线程创建成功之后的代码

8、newThread

public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
    return new ForkJoinWorkerThread(pool);
}

protected ForkJoinWorkerThread(ForkJoinPool pool) {
    // Use a placeholder until a useful name can be set in registerWorker
    super("aForkJoinWorkerThread");
    this.pool = pool;
    1、主要方法在这儿绑定当前线程与队列
    this.workQueue = pool.registerWorker(this);
}

9、registerWorker

final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        UncaughtExceptionHandler handler;
    	1、线程为守护线程、设置异常处理机制
        wt.setDaemon(true);                           // configure thread
        if ((handler = ueh) != null)
            wt.setUncaughtExceptionHandler(handler);
    	2、正式绑定了线程与工作队列
        WorkQueue w = new WorkQueue(this, wt);
        int i = 0;                                    // assign a pool index
    	3、获取队列的模式
        int mode = config & MODE_MASK;
        int rs = lockRunState();
        try {
            WorkQueue[] ws; int n;                    // skip if no array
            if ((ws = workQueues) != null && (n = ws.length) > 0) {
                4、一种索引下标计算方法,减少冲突
                int s = indexSeed += SEED_INCREMENT;  // unlikely to collide
                int m = n - 1;
                5、计算出来的i为奇数
                i = ((s << 1) | 1) & m;               // odd-numbered indices
                if (ws[i] != null) {                  // collision
                    6、发生冲突,若循环一圈都冲突进行扩容
                    int probes = 0;                   // step by approx half n
                    int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                    while (ws[i = (i + step) & m] != null) {
                        if (++probes >= n) {
                            workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                            m = n - 1;
                            probes = 0;
                        }
                    }
                }
                7、将workQueue放入到数组中
                w.hint = s;                           // use as random seed
                w.config = i | mode; //索引 | 模式
                //i 为奇数,说明workQueue为扫描中
                w.scanState = i;                      // publication fence
                ws[i] = w;
            }
        } finally {
            unlockRunState(rs, rs & ~RSLOCK);
        }
        wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
        return w;
    }

首先明确线程池内的线程都是 守护线程 ,所以内部并没有手动的去关闭线程,而是通过runState状态来判断是否关闭了线程池。
该方法主要就是将线程与workQueue进行绑定,放入到数组的奇数位索引处,准备扫描任务执行。
绑定之后就可以开始执行线程 start 方法进入。

七、正式开始执行线程

1、run

    public void run() {
        if (workQueue.array == null) { // only run once
            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);
                }
            }
        }
    }

    final void runWorker(WorkQueue w) {
        1、初始化线程数组
        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;;) {
            2、扫描任务,若扫描到开始执行任务
            if ((t = scan(w, r)) != null)
                w.runTask(t);
            3、未扫描到任务,等待任务
            else if (!awaitWork(w, r))
                break;
            r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
        }
    }

  • 扫描任务,若扫描到任务开始执行任务
  • 未扫描到任务进行等待,一直等待都没有扫描到任务

2、scan窃取方法

1、w是刚才的创建的奇数位的workQueue,r用于窃取队列索引的随机种子   
private ForkJoinTask<?> scan(WorkQueue w, int r) {
        WorkQueue[] ws; int m;
        if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
            2、先获取一下w的扫描状态
            int ss = w.scanState;                     // initially non-negative
            3、origin就是随机要被窃取的队列的索引
            for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
                WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
                int b, n; long c;
                if ((q = ws[k]) != null) {
                    if ((n = (b = q.base) - q.top) < 0 &&
                        (a = q.array) != null) {      // non-empty
                        4、进入这儿前面必须满足,索引k处存在队列、存在要执行的任务
                        long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
					  5、从i处获取到任务,并且q.base == b的意思是当前要窃取task没有被其他线程窃取,若其他线程窃取了,base就变了,自己可能窃取的task就无效了
                        if ((t = ((ForkJoinTask<?>)
                                  U.getObjectVolatile(a, i))) != null &&
                            q.base == b) {
                            5.1、ss>0说明当前线程是激活状态
                            if (ss >= 0) {
                                if (U.compareAndSwapObject(a, i, t, null)) {
                                    5.2、cas修改i处的任务为null,base++
                                    q.base = b + 1;
                                    5.3、如果n小于-1 说明等待的task大于1,尝试唤醒或创建线程
                                    if (n < -1)       // signal others
                                        signalWork(ws, q);
                                    5.4、返回任务,开始执行了
                                    return t;
                                }
                            }
                            6、oldSum==0说明扫描到任务了,但当前workQueue未激活,尝试激活线程
                            else if (oldSum == 0 &&   // try to activate
                                     w.scanState < 0)
                                tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                        }
                        //刷新ss的状态,因为有可能其他线程激活了该workQueue
                        if (ss < 0)                   // refresh
                            ss = w.scanState;
                        7、换个位置重新扫描,continue继续下次循环
                        r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                        origin = k = r & m;           // move and rescan
                        oldSum = checkSum = 0;
                        continue;
                    }
                    checkSum += b;
                }
                8(k = (k + 1) & m) == origin说明origin索引处竞争度低,且worker是激活状态,由于k每次都加一,当相等时,workQueues数组元素被遍历了一遍,但是仍没有获取到task
                if ((k = (k + 1) & m) == origin) {    // continue until stable
                    8.1、如果进入if语句,将workQueue修改为未激活状态,这儿只是状态设置为未激活,线程还要继续自旋,且又继续遍历了一遍workQueues数组才会用到第二个条件ss == (ss = w.scanState),这儿就是判断其他线程有没有修改该workQueue的状态,若没有其他线程影响,继续oldSum == (oldSum = checkSum)这儿要满足说明在本次对workQueues遍历时,索引k处获取的q始终为null,也就是没有获取到过工作队列。
                       如果一直都能获取到任务,说明任务较多,未激活的任务又会被激活。
                       如果一直扫描到为null的队列,退出循环,交由awaitWork方法处理。
                    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;
    }

1、先观察是否能获取到task,若能获取到通过cas修改变量值成功返回任务开始执行任务。

2、若获取到了task但是当前workQueue未激活,尝试唤醒它,因为任务来了要开始工作啦

3、获取不到任务并且扫描了workQueue数组两圈,并且其他线程没有干扰scanState的值,最终进入break,交由awaitWork方法处理,看是否进一步的阻塞还是终止线程。

3、runTask方法

        final void runTask(ForkJoinTask<?> task) {
            if (task != null) {
                1、状态改成工作状态,其实就是改成了偶数
                scanState &= ~SCANNING; // mark as busy
                2、开始执行当前的任务,在这儿正式进入自己实现的 compute()方法
                (currentSteal = task).doExec();
                U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
                3、执行当前workQueue的其他任务,这儿也展示了究竟是FIFI以及LIFO模式
                execLocalTasks();
                ForkJoinWorkerThread thread = owner;
                if (++nsteals < 0)      // collect on overflow
                    transferStealCount(pool);
                scanState |= SCANNING;
                if (thread != null)
                    thread.afterTopLevelExec();
            }
        }
  • workQueue的状态改成繁忙状态,然后开始执行当前的task
  • 当前队列中还有其他任务,则按照设置的模式(LIFO&FIFO)获取任务来执行
  • 任务执行完修改是否是窃取的任务执行的,然后记录一下,最后将workQueue的状态改回去

4、execLocalTasks

        final void execLocalTasks() {
            int b = base, m, s;
            ForkJoinTask<?>[] a = array;
            1、当前workQueue还有未执行的任务
            if (b - (s = top - 1) <= 0 && a != null &&
                (m = a.length - 1) >= 0) {
                2、为true则是LIFO模式
                if ((config & FIFO_QUEUE) == 0) {
                    for (ForkJoinTask<?> t;;) {
                        2.1、获取顶部也就是最后一个进入的任务,然后开始执行
                        if ((t = (ForkJoinTask<?>)U.getAndSetObject
                             (a, ((m & s) << ASHIFT) + ABASE, null)) == null)
                            break;
                        U.putOrderedInt(this, QTOP, s);
                        t.doExec();
                        2.2、执行完top-1
                        if (base - (s = top - 1) > 0)
                            break;
                    }
                }
                else
                    3、否则就是FIFO模式吗,从底部获取任务。
                    pollAndExecAll();
            }
        }

执行自己workQueue的任务,根据预设的模式进行获取任务然后开始执行。

5、awaitWork休眠与终结

private boolean awaitWork(WorkQueue w, int r) {
    	1、w为null或者,w已经终止,返回false就不会继续扫描了
        if (w == null || w.qlock < 0)                 // w is terminating
            return false;
        for (int pred = w.stackPred, spins = SPINS, ss;;) {
            2、其他线程将w又激活了
            if ((ss = w.scanState) >= 0)
                break;
            3、空转,啥也不干拖延时间
            else if (spins > 0) {
                r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
                if (r >= 0 && --spins == 0) {         // randomize spins
                    WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
                    if (pred != 0 && (ws = workQueues) != null &&
                        (j = pred & SMASK) < ws.length &&
                        (v = ws[j]) != null &&        // see if pred parking
                        (v.parker == null || v.scanState >= 0))
                        spins = SPINS;                // continue spinning
                }
            }
            4、再次检查w状态
            else if (w.qlock < 0)                     // recheck after spins
                return false;
            5、不是中断状态
            else if (!Thread.interrupted()) {
                long c, prevctl, parkTime, deadline;
                5.1、获取ac,也就是活跃线程的数量
                int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
                5.2、已经没有活跃的线程,并且终止线程池成功或线程池已经关闭,直接结束自旋
                if ((ac <= 0 && tryTerminate(false, false)) ||
                    (runState & STOP) != 0)           // pool terminating
                    return false;
                5.3、没有活跃线程,当前的work是workQueues数组最后一个
                if (ac <= 0 && ss == (int)c) {        // is last waiter
                    prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
                    int t = (short)(c >>> TC_SHIFT);  // shrink excess spares
                    5.4、注意此时线程池并没有关闭,休眠的线程在等待新的任务到来,以此达到线程的复用,所以休眠的线程不宜太多,休眠线程的数量大于2,通过cas修改ctl的值为pre,则准备关闭work栈的顶部的线程
                    if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
                        return false;                 // else use timed wait
                    parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
                    5.5、计算阻塞的时间
                    deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
                }
                else
                    prevctl = parkTime = deadline = 0L;
                Thread wt = Thread.currentThread();
                U.putObject(wt, PARKBLOCKER, this);   // emulate LockSupport
                w.parker = wt;
                6、再次检查w状态以及ctl是否改变
                if (w.scanState < 0 && ctl == c)      // recheck before park
                    U.park(false, parkTime);
                7、有两种方式唤醒:1、阻塞时间到了自动唤醒;2、被其他线程激活重新唤醒
                U.putOrderedObject(w, QPARKER, null);
                U.putObject(wt, PARKBLOCKER, null);
                8、线程再次被激活,继续去扫描任务
                if (w.scanState >= 0)
                    break;
                9、已经设置了阻塞时间 && ctl再次期间没有改变 && 阻塞时间到了自动唤醒 && cas设置ctl为前一个成功
                    则返回false,终止线程
                if (parkTime != 0L && ctl == c &&
                    deadline - System.nanoTime() <= 0L &&
                    U.compareAndSwapLong(this, CTL, c, prevctl))
                    return false;                     // shrink pool
            }
        }
        return true;
    }

1、只要是workQueue被激活了,就要继续扫描任务来干活,否则就要被阻塞或者被终止

2、休眠的线程太多了,直接终止最后一个创建的workQueue线程

3、线程阻塞之后,没有被其他线程唤醒,并且ctl在此期间没有改变,不在继续扫描,终止线程。

八、tryTerminate 线程池终止方法

    /**
     *
     * @param now 如果为true则无条件终止,反之等待没有task以及没有活跃的workQueue
     * @param enable 若为false,则如果runState为非SHUTDOWN状态时不允许关闭,若为true则结合now进行处理。
     * @return true 表示正在关闭或者已经关闭
     */
private boolean tryTerminate(boolean now, boolean enable) {
        int rs;
    	1、如果是公共的线程池则不允许关闭
        if (this == common)                       // cannot shut down
            return false;
    	2、大于0说明runState不是SHUTDOWN状态
        if ((rs = runState) >= 0) {
            2.1、如果enable为false,直接返回
            if (!enable)
                return false;
            2.2、设置runState为SHUTDOWN状态,这是外部将不允许提交任务
            rs = lockRunState();                  // enter SHUTDOWN phase
            unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
        }
		3、前面已经将runState ---> SHUTDOWN状态,这儿是SHUTDOWN--->STOP状态
           如果有休眠的线程,唤醒,其实就是加快处理任务,处理完之后再次进入这儿转换为STOP状态
        if ((rs & STOP) == 0) {
            4、不是立刻关闭,先处理任务
            if (!now) {                           // check quiescence
                for (long oldSum = 0L;;) {        // repeat until stable
                    WorkQueue[] ws; WorkQueue w; int m, b; long c;
                    long checkSum = ctl;
                    4.1、是否还存在活跃的线程,存在线程先执行任务
                    if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0)
                        return false;             // still active workers
                    4.2、workQueues数组已经为null或者没有workQueue了,直接结束
                    if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
                        break;                    // check queues
                    4.3、遍历workQueues数组,如果workQueue还存在task或还活跃则尝试唤醒线程快速处理task
                    for (int i = 0; i <= m; ++i) {
                        if ((w = ws[i]) != null) {
                            if ((b = w.base) != w.top || w.scanState >= 0 ||
                                w.currentSteal != null) {
                                tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                                return false;     // arrange for recheck
                            }
                            checkSum += b;
                            4.4、将偶数位索引workQueue设置为终止状态,其实就是不允许外部提交任务
                            if ((i & 1) == 0)
                                w.qlock = -1;     // try to disable external
                        }
                    }
                    4.5、上面遍历的ws都为null,则进入这儿break
                    if (oldSum == (oldSum = checkSum))
                        break;
                }
            }
            5、直接关闭,不用考虑是否还存在任务
            if ((runState & STOP) == 0) {
                rs = lockRunState();              // enter STOP phase
                unlockRunState(rs, (rs & ~RSLOCK) | STOP);
            }
        }
		6、pass for循环的次数
        int pass = 0;                             // 3 passes to help terminate
        for (long oldSum = 0L;;) {                // or until done or stable
            WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; int m;
            long checkSum = ctl;
            6.1、没有线程了 || workQueues不存在了
            if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 ||
                (ws = workQueues) == null || (m = ws.length - 1) <= 0) {
                6.2、将runState设置为终止状态,唤醒所有wait()的线程,让他们赶紧执行完毕释放线程资源
                if ((runState & TERMINATED) == 0) {
                    rs = lockRunState();          // done
                    unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED);
                    synchronized (this) { notifyAll(); } // for awaitTermination
                }
                break;
            }
            7、遍历ws
            for (int i = 0; i <= m; ++i) {
                if ((w = ws[i]) != null) {
                    checkSum += w.base;
                    7.1、将workQueue设置为终止状态
                    w.qlock = -1;                 // try to disable
                    if (pass > 0) {
                        7.2、释放所有的任务
                        w.cancelAll();            // clear queue
                        if (pass > 1 && (wt = w.owner) != null) {
                            if (!wt.isInterrupted()) {
                                try {             // unblock join
                                    7.3、其实是唤醒线程的一种手段,目的也是尽快让线程释放资源
                                    wt.interrupt();
                                } catch (Throwable ignore) {
                                }
                            }
                            7.4、唤醒线程的一种手段,目的也是尽快让线程释放资源
                            if (w.scanState < 0)
                                U.unpark(wt);     // wake up
                        }
                    }
                }
            }
            8、上面扫描到workQueue则需要刷新循环次数
            if (checkSum != oldSum) {             // unstable
                oldSum = checkSum;
                pass = 0;
            }
            9、循环大于3次直接终止
            else if (pass > 3 && pass > m)        // can't further help
                break;
            10、pass++
            else if (++pass > 1) {                // try to dequeue
                long c; int j = 0, sp;            // bound attempts
                11、再次检查是否存在睡眠的workQueue,唤醒尽快让线程释放资源
                while (j++ <= m && (sp = (int)(c = ctl)) != 0)
                    tryRelease(c, ws[sp & m], AC_UNIT);
            }
        }
        return true;
    }

状态转换:runState —>SHUTDOWN—>STOP —>TERMINATED。

SHUTDOWN: 如果now为false,只要是存在task或活跃的线程就继续执行完任务在进行终止,但此时外部已经无法提交新的任务,线程还会继续执行以及扫描窃取任务,反之最终会将状态设置为STOP。

STOP: 一共会循环四轮

  • 第一轮:将所有的workQueueqlock设置为-1,表示workQueue已经终止,若没有线程竞争其实workQueues数组已经不存在元素了,这儿是有线程安全问题恰好有线程提交了任务。(7.1)
  • 第二轮:取消所有任务,激活所有休眠的线程。(11)
  • 第三轮:中断线程以及唤醒线程。(7.3、7.4)
  • 第四轮结束。(9)

终止线程其实就是让线程将run方法赶紧执行完毕或中断抛出异常结束,释放资源。

九、总结

1、在调用 invoke、execute、submit 方法后开始执行

2、首先执行简易版的外部提交,已经初始化等条件,直接将任务放到偶数索引位的work任务数组中,等待线程窃取任务。(externalPush方法

3、否则进入externalSubmit方法执行完整的流程,workQueue数组初始化—> 创建偶数索引的work—>将任务放到该位置,然后发出信号来任务了(signalWork)。

4、接着要么创建新的 守护线程 、要么唤醒休眠的线程开始执行任务。

5、创建线程同时会创建一个wokerQueue与线程绑定放到workQueue数组的奇数位索引,接着开始执行线程。

6、进入runWorker方法 自旋的扫描任务,若扫描成功开始执行任务,若扫描失败进入awaitWork方法 考虑是否阻塞线程或终止线程。

7、最后终止线程让线程正常run结束或中断阻塞的线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值