Java 线程池体系 - ForkJoinPool

ForkJoinPool

一 . 推理

1. 思考

(1) 思考一 :

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

(2) 思考二 :

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

(3) 思考三 :

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

2. 思考图

问题

3. 问题 :

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

二 . 架构

1. 理解图

在这里插入图片描述

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)。

未完待续

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值