Java线程池实现原理(三)

转自美团技术团队

线程池生命周期管理

上一期讲了线程池生命周期管理的任务管理,对于线程池来说,任务管理和线程管理是它必须关注的事情,而线程管理比任务管理则更复杂。

线程管理

Worker线程(工作线程)

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker,先看一下源码:

1
2
3
4
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}

Worker这个线程,实现了Runnable接口,并持有一个线程thread变量,一个初始化的任务firstTask,thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值时非空的,那么线程就会在启动初期立即执行这个任务,也就是对应核心线程(corePool)创建时的情况,如果这个值时null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建

Worker执行任务的模型如下图所示:

线程池需要管理线程的生命周期,需要在线程池长时间不允许的时候进行回收。线程池使用一张Hash表去持有线程的引用,这样可以通过添加引用,移除引用这样的操作来控制线程的生命周期,这个时候重要的是如何判断线程是否在运行。

Worker是通过继承AQS,使用AQS来实现独占锁这个功能(每次只能有一个线程能持有锁,是针对共享锁而言的,AQS源码中其内部类Node定义来两个常量SHARED和EXCLUSIVE,分别就是共享模式和独占模式,标示了AQS队列中等待线程的锁获取模式),为的就是实现不可重入的特性去反应线程当前的执行状态。

下图是Java中主流锁的分类:(锁的概念有很多,但是其实各种锁的维度是不同的,后面会详细深入了解)

java各种主流锁分类

那么如何判断线程是否正在运行呢:

  1. lock方法一旦获取了独占锁,表示当前线程正在执行任务中
  2. 如果正在执行任务,则不应该中断线程
  3. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断
  4. 线程池在执行shutdown方法或者tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法使用tryLock方法来判断线程池中的线程是否空闲状态;如果是空闲状态则可以安全回收。

会在线程回收过程中也使用到了这种特性,回收过程如下图所示:

Worker线程增加

增加线程时通过线程池中addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。 addWorker方法有两个参数:firstTask,core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空(和Worker线程中的firstTask差不多),core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize(核心线程数),false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize(最大线程数),其执行流程如下:

Worker线程回收

线程池中线程的销毁依赖于JVM自动的回收,线程池做的工作时根据当前线程池的状态维护一定数量的线程引用(前面提到的用Hash表),防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。Worker被创建出来后,就会不断的进行轮训,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环就会结束,Worker会主动消除自身在线程池内的引用。

1
2
3
4
5
6
7
try {
while (task != null || (task = getTask()) != null) {
//执行任务(循环)
}
} finally {
processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}

上述代码可以看出线程池回收时在processWorkerExit方法中完成的,下面是线程回收流程图:

实时上,在这个方法中,将线程引用移除线程池就已经结束线程池销毁线程的工作了(等待JVM自动回收线程)。但是由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的状态,是否要根据新状态,重新分配线程。

Woker线程执行任务

在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法执行过程是:

  1. while循环不断通过getTask方法来获取任务
  2. getTask方法总阻塞队列中获取任务
  3. 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。
  4. 执行任务
  5. 如果getTask方法结果为null,则跳出循环,执行processWorkerExit方法,销毁线程

流程图如下: