ForkJoinPool

一 . 推理

1. 思考

(1) 思考一 :

ForkJoinPool 出现的原因?为什么不用ThreadPoolExecutor?

(2) 思考二 :

当待执行的任务,执行前提是完成其他任务(链式任务,该任务的前提还有子任务)该如何处理?

(3) 思考三 :

在ThreadPoolExecutor中的上述任务所处线程被阻塞了,会出现什么情况?

2. 思考图

java 如何用线程池处理集合LIST数据_ForkJoinPool

3. 问题 :

  • 如果使用固有的ThreadPoolExecutor及衍生类
  • 其一,会使工作量变得巨大耗费性能
  • 其二,需要业务手动维护任务之间的衔接(子任务线程的创建等或存入任务队列),及其他处理
  • 其三,在子任务中完成工作需要创建线程完成前置任务,会占用固有线程数,如果线程池线程数与任务队列已经满载,即子任务无法进行,会面临无限期阻塞

二 . 架构

1. 理解图

java 如何用线程池处理集合LIST数据_ForkJoinPool_02

2. 缺省版关键代码分析
(1) . 步骤一 : 调用ForkJoinPool

通过ForkJoinPool执行最简单的普通任务

ForkJoinPool forkJoinPool = new ForkJoinPool();
    forkJoinPool.submit(()->{System.out.println("Hello Word");});
    forkJoinPool.shutdown();
    try {
      forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
(2) . 步骤二: submit()方法

将任务包装成ForkJoinPool 特有的任务形式,然后交由externalPush()提交任务到所属队列

public ForkJoinTask<?> submit(Runnable task) {
        ForkJoinTask<?> job;
        if (task instanceof ForkJoinTask<?>)
            job = (ForkJoinTask<?>) task;
        else
            job = new ForkJoinTask.AdaptedRunnableAction(task);
        externalPush(job);
        return job;
    }
(3) . 步骤三: externalPush()方法

由于首次执行,workQueues为null,即if条件不成立,所以直接执行最后一步externalSubmit(task);

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 && (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)) { int j = ((am & s) << ASHIFT) + ABASE; U.putOrderedObject(a, j, task); U.putOrderedInt(q, QTOP, s + 1); U.putIntVolatile(q, QLOCK, 0); if (n <= 1) signalWork(ws, q); return; } U.compareAndSwapInt(q, QLOCK, 1, 0); } externalSubmit(task); }

  • 简化后
final void externalPush(ForkJoinTask<?> task) {
    	externalSubmit(task);
	}
(4) . 步骤四 : externalSubmit() 方法

真正执行外部提交得任务得方法

(4.1) 简化版,方法概要
private void externalSubmit(ForkJoinTask<?> task) {
        for (;;) {
            //[1.0]如果线程池已经关闭
            if ((rs = runState) < 0) {...}
            //[2.0]如果线程池还没有初始化
            else if ((rs & STARTED) == 0 || 
                     ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {...}
            //[3.0]如果线程池正在运行
            else if ((q = ws[k = r & m & SQMASK]) != null) {...}
            //[4.0]如果找到的这个workQueues没有被创建,则创建
            else if (((rs = runState) & RSLOCK) == 0) {...}
            //[5.0]发生竞争时,让当前线程选取其他的workQueues来重试
            else
            	move = true;
	        if (move)//重新获取下一个不同得随机数
	            r = ThreadLocalRandom.advanceProbe(r);
	        }
	    }
    }
(4.2) 完整版,方法详解
private void externalSubmit(ForkJoinTask<?> task) {
    //------------------取随机数-------------------------------------
    int r;
    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);
            throw new RejectedExecutionException();
        }
        else if ((rs & STARTED) == 0 || //--------STARTED 状态位尚未置位(状态位初始化为1),需要初始化
                 ((ws = workQueues) == null || (m = ws.length - 1) < 0)) { // -------workQueues,需要初始化
            //****************如果if条件不成立,但是由于||运算执行了所有的条件语句,使得ws与m变量已经初始化
            int ns = 0;
            rs = lockRunState(); //上锁,如果没上锁,则等待
            try {
                if ((rs & STARTED) == 0) { //------防止多线程操作,已经被初始化,所以再次判断STARTED状态,防止无效CAS
                    U.compareAndSwapObject(this, STEALCOUNTER, null,
                                           new AtomicLong());

					//并行度,即线程数量,大小最小为4 (当传入为1时得4)
                    int p = config & SMASK; 
                    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;
                    //初始化工作队列(偶数为外部提交队列,奇数为工作窃取队列)
                    workQueues = new WorkQueue[n];
                    //初始化队列成功,将状态变为STARTED
                    ns = STARTED;
                }
            } finally {
            //执行完成,释放锁
                unlockRunState(rs, (rs & ~RSLOCK) | ns);
            }
        }
        // 在上一个分支执行初始化之后,
        // 第二次循环将会到达这里。
        // 由于全局队列workQueues种存储了两种队列:外部提交队列、内部工作队列(任务窃取队列)
        // 那么这时,应该去找到本线程对应任务应该存储在哪个外部提交队列里
        // 则通过上面获取的随机种子r,来找到对应任务应该放在哪里
        // SQMASK = 1111110,所以由SQMASK的前面的1来限定长度,末尾的0来表明,外部提交队列一定在偶数位
        else if ((q = ws[k = r & m & SQMASK]) != null) {
        	// 由于当前提交队列是外部提交队列,那么一定会有多线程共同操作,那么为了保证并发安全,那么这里需要上锁,也即对当前提交队列进行锁定
            if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
             	// 取提交队列的保存任务的数组array
                ForkJoinTask<?>[] a = q.array;
                int s = q.top;
                boolean submitted = false; 
                try {
                    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标志位,
            // 让线程在下一次循环的时候,重新计算随机数,让它寻找另外的队列。
            move = true;
        }
        // 如果找到的这个任务队列没有被创建,则创建,但是,这里的RSLOCK的判断,在于,当没有别的线程持有RSLOCK的时候,才会进入。这是由于RSLOCK主管,runstate,可能有别的线程把状态改了,根本不需要再继续work了
        else if (((rs = runState) & RSLOCK) == 0) {
        	// 创建外部提交队列,由于ForkJoinWorkerThread 内部线程为null,所以为外部提交队列
            q = new WorkQueue(this, null);
            // r为什么保存为hint,r是随机数,通过r找到当前外部提交队列,处于WQS的索引下标
            q.hint = r;
            // SHARED_QUEUE = 1 << 31;这里就是将整形int的符号位置1,所以为负数,SHARED_QUEUE表明当前队列是共享队列(外部提交队列)
            q.config = k | SHARED_QUEUE;
            // 由于当前任务队列并没有进行扫描任务,所以扫描状态位无效状态INACTIVE
            q.scanState = INACTIVE;
            
            rs = lockRunState(); 
            // 确保线程池处于运行状态
            // 由于可能两个线程同时进来操作,只有一个线程持有锁,那么只允许一个线程放创建的队列,但是这里需要注意的是:可能会有多个线程创建了WorkQueue,但是只有一个能成功
            if (rs > 0 &&  (ws = workQueues) != null &&
                k < ws.length && ws[k] == null){
                //将其放入全局任务队列种
                ws[k] = q;
                }
                //解锁
            unlockRunState(rs, rs & ~RSLOCK);
        }
        else
            move = true;
        if (move)
            r = ThreadLocalRandom.advanceProbe(r);
    }
}

  • 任务处理之 fork()方法
public final ForkJoinTask<V> fork() {
        Thread t;
        //如果当前任务是内部任务则添加进内部队列
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
        //如果当前任务是外部任务则添加进外部队列
            ForkJoinPool.common.externalPush(this);
        return this;
    }
  • 任务存储之 WorkQueue类
@sun.misc.Contended
static final class WorkQueue {

      static final int INITIAL_QUEUE_CAPACITY = 1 << 13;

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

      volatile int scanState;    // versioned, <0: inactive; odd:scanning
      int stackPred;             // pool stack (ctl) predecessor
      int nsteals;               // number of steals
      int hint;                  // randomization and stealer index hint
      int config;                // pool index and mode
      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;   // the elements (initially unallocated)
      final ForkJoinPool pool;   // the containing pool (may be null)
      final ForkJoinWorkerThread owner; // owning thread or null if shared
      volatile Thread parker;    // == owner during call to park; else null
      volatile ForkJoinTask<?> currentJoin;  // task being joined in awaitJoin
      volatile ForkJoinTask<?> currentSteal; // mainly used by helpStealer

      WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
          this.pool = pool;
          this.owner = owner;
          // 初始化队列base与top
          base = top = INITIAL_QUEUE_CAPACITY >>> 1;
      }
}

@sun.misc.Contended 是 Java 8 新增的一个注解,对某事物加上该注解则表示该事物会单独占用一个缓存行(Cache Line)。

未完待续