【源码粗读】 - ThreadPool[2] - 底层的苦力Worker

218 阅读5分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

worker

在之前的描述中我们已经清楚:

  • worker是线程池中,对于第一个线程状态的封装。

我们看一下worker的签名:

 private final class Worker
     extends AbstractQueuedSynchronizer
     implements Runnable
 {

注意:worker继承并实现了Runnable接口;

我们是通过以下的构造方法来构建worker的:

 Worker(Runnable firstTask) {
     setState(-1); // inhibit interrupts until runWorker
     this.firstTask = firstTask;
     this.thread = getThreadFactory().newThread(this);
 }

那么说明:

  • 当我们使用 worker(null) 进行构建的时候,我们的任务信息是没有进入到worker中的。

注意,此时从getThreadFactory().newThread(this) 方法中获取并被封装到worker中的线程,线程中封装的是worker任务,上面描述的:

t.start()

实际上启动的就是worker的run方法了。

接下来看一下worker的run方法有什么不一样的地方:

 public void run() {
     runWorker(this);
 }
 ​
 final void runWorker(Worker w) {
         Thread wt = Thread.currentThread();
         Runnable task = w.firstTask;
         w.firstTask = null;
         w.unlock();
         boolean completedAbruptly = true;
         try {
             while (task != null || (task = getTask()) != null) {
                 w.lock();
                 if ((runStateAtLeast(ctl.get(), STOP) ||
                      (Thread.interrupted() &&
                       runStateAtLeast(ctl.get(), STOP))) &&
                     !wt.isInterrupted())
                     wt.interrupt();
                 try {
                     beforeExecute(wt, task);
                     Throwable thrown = null;
                     try {
                         task.run();
                     } catch (RuntimeException x) {
                         thrown = x; throw x;
                     } catch (Error x) {
                         thrown = x; throw x;
                     } catch (Throwable x) {
                         thrown = x; throw new Error(x);
                     } finally {
                         afterExecute(task, thrown);
                     }
                 } finally {
                     task = null;
                     w.completedTasks++;
                     w.unlock();
                 }
             }
             completedAbruptly = false;
         } finally {
             processWorkerExit(w, completedAbruptly);
         }
     }
     
     
     private Runnable getTask() {
         boolean timedOut = false; // Did the last poll() time out?
 ​
         for (;;) {
             int c = ctl.get();
             int rs = runStateOf(c);
 ​
             // Check if queue empty only if necessary.
             if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                 decrementWorkerCount();
                 return null;
             }
 ​
             int wc = workerCountOf(c);
 ​
             // Are workers subject to culling?
             boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
 ​
             if ((wc > maximumPoolSize || (timed && timedOut))
                 && (wc > 1 || workQueue.isEmpty())) {
                 if (compareAndDecrementWorkerCount(c))
                     return null;
                 continue;
             }
 ​
             try {
                 Runnable r = timed ?
                     workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                     workQueue.take();
                 if (r != null)
                     return r;
                 timedOut = true;
             } catch (InterruptedException retry) {
                 timedOut = false;
             }
         }
     }

核心是:

  • 一旦启动了worker,就会以当前线程,来启动任务:

    Thread wt = Thread.currentThread();

随后判断是否有指定的firstTask任务,如果没有,就执行 getTask() 方法来获取任务,不管任务获取到与否都会循环这个过程:

while (task != null || (task = getTask()) != null)

执行完获取到的线程后,会回来继续获取线程来进行工作

getTask里:

  • 先判断:

    • 当前线程池是不是挂了,或者工作队列空了?
    • 上次获取是不是超时了?核心线程超时了?
  • 前面的条件如果通过了,就根据指定的 allowCoreThreadTimeOut,来进行限时获取工作,或者不限时地获取

循环这个过程,直到判断不通过,或者获取到线程为止。

到这里我们就明白为什么说线程复用了:

  • 线程池维护了一个workQueue,一旦我们提交了任务,那么:

    • 如果是核心线程:我们提交的这个任务会被封装成worker对象,在线程池中永无止境地工作:

      • 提交的任务结束后,会继续持续地去获取workQueue中的任务来运行。

我们也可以解释为什么有的地方会执行:

addWorker(null, false);

那么线程池空闲线程的回收机制是如何实现的?

现在我们知道:

  • 当worker执行完firstTask之后,会通过getTask()方法,来获取任务去执行。

注意getTask()里的这段代码:

 if ((wc > maximumPoolSize || (timed && timedOut))
                 && (wc > 1 || workQueue.isEmpty())) {
                 if (compareAndDecrementWorkerCount(c))
                     return null;

如果设置了超时相关配置,并且工作队列已经空了,此时就会返回一个null。

回到worker的runWorker方法中,当没有任务可以获取到时,最后就会执行这个方法:

 private void processWorkerExit(Worker w, boolean completedAbruptly) {
         if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
             decrementWorkerCount();
 ​
         final ReentrantLock mainLock = this.mainLock;
         mainLock.lock();
         try {
             completedTaskCount += w.completedTasks;
             workers.remove(w);
         } finally {
             mainLock.unlock();
         }
 ​
     //注意,这里是根据ctl的值进行判断的,因此并不意味着worker退出的时候就一定会去尝试关闭线程池
         tryTerminate();
 ​
         int c = ctl.get();
         if (runStateLessThan(c, STOP)) {
             if (!completedAbruptly) {
                 int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                 if (min == 0 && ! workQueue.isEmpty())
                     min = 1;
                 //注意这里:如果比核心线程的数量多,那么就不新增了,而是直接返回。
                 if (workerCountOf(c) >= min)
                     return; // replacement not needed
             }
             addWorker(null, false);
         }
     }

这里就能看出来线程的回收,实际上就是在线程池未彻底关闭的情况下,将worker移出工作队列,随后添加一个新的worker来执行任务。

通过这两篇文章,解释了我们常见对于线程池的描述:

  • 线程池三个大小的描述:(execute方法中都描述了

    • 核心线程(corePoolSize)未满的时候,是直接添加线程进行工作的(addWorker(task,true)

    • 线程池在核心线程满而工作队列(workQueue)未满的时候,会往工作队列中添加任务,而不是添加worker

      workQueue.offer(command)

    • 线程池工作队列满了就会继续添加worker,失败则调用拒绝策略

      else if (!addWorker(command, false)){reject(command);}

      • 此处和线程池最大大小(maximumPoolSize)的相关性在addWorker方法中

        if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;

  • 线程池工作线程的描述:

    • 线程池的线程复用:

      • 实际上线程池中,工作线程被封装为worker,在工作执行完之后会去工作队列中获取新的任务来执行(getTask)

      • 等待超时的线程会被回收。

        • 在worker的runWorker方法中,当获取不到任务的时候就会调用processWorkerExit方法并退出runWorker方法的执行,在processWorkerExit方法中,会将该worker从维护worker的workers中剔除。

        • 此时如果工作的线程数量超过了核心线程数[1] ,那么就返回了,相当于注销了这个工作线程;如果没有,会新增一个没有firstTask的工作线程,随后使用addWorker(null,false)

          • 如果通过allowCoreThreadTimeOut设置了不允许核心线程超时,那么上面的核心线程数[1] 的地方就是0了。

          • 此时需要注意:我们新添加的worker对象,是非核心的,并且此处没有添加锁等机制

            • 非核心线程中,对应的判断退出与否是:

              int wc = workerCountOf(c);

              if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;

              也就意味着:此处可能会出现添加了worker之后,出现了工作线程数大于corePoolSize的情况,但这并没有什么太大的问题,因为此时工作队列是空的,如果获取不到任务自然会进行这部分worker对象的回收。

  • 线程复用是错误的:

    • 通过上面的描述我们可以看到,实际上复用的是worker对应的线程,而worker本身也是一种现成的继承类,因此本身这种说法就模棱两可:

      你既可以说复用的是worker对象,也可以说复用的是(worker所使用的)线程,只是看描述的维度如何罢了。

\