了解下JUC的线程池学习七(getTask方法源码分析)

1.介绍

   getTask()方法是工作线程在while死循环中获取任务队列中的任务对象的方法

 

2.源码

   private Runnable getTask() {
       // 记录上一次从队列中拉取的时候是否超时
       boolean timedOut = false; // Did the last poll() time out?
       // 注意这是死循环
       for (;;) {
           int c = ctl.get();

          // Check if queue empty only if necessary.
          // 第一个if:如果线程池状态至少为SHUTDOWN,也就是rs >= SHUTDOWN(0),则需要判断两种情况(或逻辑):
          // 1. 线程池状态至少为STOP(1),也就是线程池正在停止,一般是调用了shutdownNow()方法
          // 2. 任务队列为空
          // 如果在线程池至少为SHUTDOWN状态并且满足上面两个条件之一,则工作线程数wc减去1,然后直接返回null
          if (runStateAtLeast(c, SHUTDOWN)
              && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
                 decrementWorkerCount();
                 return null;
           }
           // 跑到这里说明线程池还处于RUNNING状态,重新获取一次工作线程数
           int wc = workerCountOf(c);

           // Are workers subject to culling?
           // timed临时变量勇于线程超时控制,决定是否需要通过poll()此带超时的非阻塞方法进行任务队列的任务拉取
           // 1.allowCoreThreadTimeOut默认值为false,如果设置为true,则允许核心线程也能通过poll()方法从任务队列中拉取任务
           // 2.工作线程数大于核心线程数的时候,说明线程池中创建了额外的非核心线程,这些非核心线程一定是通过poll()方法从任务队列中拉取任务
          boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
          // 第二个if:
          // 1.wc > maximumPoolSize说明当前的工作线程总数大于maximumPoolSize,说明了通过setMaximumPoolSize()方法减少了线程池容量
          // 或者 2.timed && timedOut说明了线程命中了超时控制并且上一轮循环通过poll()方法从任务队列中拉取任务为null
          // 并且 3. 工作线程总数大于1或者任务队列为空,则通过CAS把线程数减去1,同时返回null,
          // CAS把线程数减去1失败会进入下一轮循环做重试
          if ((wc > maximumPoolSize || (timed && timedOut))
              && (wc > 1 || workQueue.isEmpty())) {
               if (compareAndDecrementWorkerCount(c))
                     return null;
                     continue;
           }

          try {
               // 如果timed为true,通过poll()方法做超时拉取,keepAliveTime时间内没有等待到有效的任务,则返回null
               // 如果timed为false,通过take()做阻塞拉取,会阻塞到有下一个有效的任务时候再返回(一般不会是null)
               Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();
               // 这里很重要,只有非null时候才返回,null的情况下会进入下一轮循环
               if (r != null)
                    return r;
                   // 跑到这里说明上一次从任务队列中获取到的任务为null,一般是workQueue.poll()方法超时返回null
                   timedOut = true;
               } catch (InterruptedException retry) {
                   timedOut = false;
              }
          }
    }

 

3.分析

   方法中,有两处十分庞大的if逻辑,对于第一处if可能导致工作线程数减去1直接返回null的场景有:

  1. 线程池状态为SHUTDOWN,一般是调用了shutdown()方法,并且任务队列为空。
  2. 线程池状态为STOP

    对于第二处if,逻辑有点复杂,先拆解一下:

    // 工作线程总数大于maximumPoolSize,说明了通过setMaximumPoolSize()方法减少了线程池容量
    boolean b1 = wc > maximumPoolSize;
    // 允许线程超时同时上一轮通过poll()方法从任务队列中拉取任务为null
    boolean b2 = timed && timedOut;
    // 工作线程总数大于1
    boolean b3 = wc > 1;
    // 任务队列为空
    boolean b4 = workQueue.isEmpty();
    boolean r = (b1 || b2) && (b3 || b4);
    if (r) {
        if (compareAndDecrementWorkerCount(c)){
             return null;
        }else{
            continue;
        }
    }

  说明:这段逻辑大多数情况下是针对非核心线程。

             在execute()方法中,当线程池总数已经超过了corePoolSize并且还小于maximumPoolSize时,

             当任务队列已经满了的时候,会通过addWorker(task,false)添加非核心线程。

             而这里的逻辑恰好类似于addWorker(task,false)的反向操作,用于减少非核心线程,

             使得工作线程总数趋向于corePoolSize。如果对于非核心线程,上一轮循环获取任务对象为null

             这一轮循环很容易满足timed && timedOut为true,

            这个时候getTask()返回null会导致Worker#runWorker()方法跳出死循环,

            之后执行processWorkerExit()方法处理后续工作,而该非核心线程对应的Worker则变成“游离对象”,

            等待被JVM回收。当allowCoreThreadTimeOut设置为true的时候,这里分析的非核心线程的生命周期终结逻辑同时会适用于核心线程。

 

4.keepAliveTime的意义

   当允许核心线程超时,也就是allowCoreThreadTimeOut设置为true的时候,此时keepAliveTime表示空闲的工作线程的存活周期。

   默认情况下不允许核心线程超时,此时keepAliveTime表示空闲的非核心线程的存活周期。

 

学习来源:https://www.cnblogs.com/throwable/p/13574306.html

posted @ 2020-09-02 17:06  小窝蜗  阅读(855)  评论(0编辑  收藏  举报