Java线程池ThreadPoolExecutor源码分析
前言
ThreadPoolExecutor
是jdk
内置线程池的一个实现,基本上大部分情况都会使用这个线程池完成各项操作。
ThreadPoolExecutor
的状态和属性
ThreadPoolExecutor
线程池有5
个状态,分别是:
RUNNING
:可以接受新的任务,也可以处理阻塞队列里的任务SHUTDOWN
:不接受新的任务,但是可以处理阻塞队列里的任务STOP
:不接受新的任务,不处理阻塞队列里的任务,中断正在处理的任务TIDYING
:过渡状态,也就是说所有的任务都执行完了,当前线程池已经没有有效的线程,这个时候线程池的状态将会TIDYING
,并且将要调用terminated
方法TERMINATED
:终止状态。terminated
方法调用完成以后的状态
状态之间可以进行转换:
RUNNING -> SHUTDOWN
:手动调用shutdown
方法,或者ThreadPoolExecutor
要被GC
回收的时候调用finalize
方法,finalize
方法内部也会调用shutdown
方法
(RUNNING or SHUTDOWN) -> STOP
:调用shutdownNow
方法
SHUTDOWN -> TIDYING
:当队列和线程池都为空的时候
STOP -> TIDYING
:当线程池为空的时候
TIDYING -> TERMINATED
:terminated
方法调用完成之后
ThreadPoolExecutor
内部还保存着线程池的有效线程个数。
状态和线程数在ThreadPoolExecutor
内部使用一个整型变量保存,没错,一个变量表示两种含义。
为什么一个整型变量既可以保存状态,又可以保存数量? 分析一下:
首先,我们知道Java
中1
个整型占4
个字节,也就是32
位,所以1
个整型有32
位。
所以整型1
用二进制表示就是:00000000000000000000000000000001
整型-1
用二进制表示就是:11111111111111111111111111111111
(这个是补码,不懂的同学可以看下原码,反码,补码的知识)
在ThreadPoolExecutor
,整型中32
位的前3
位用来表示线程池状态,后29
位表示线程池中有效的线程数。
1
|
// 前3位表示状态,所以线程数占29位
|
线程池容量大小为 1 << 29 - 1 = 00011111111111111111111111111111
(二进制),代码如下
1
|
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
|
RUNNING
状态 -1 << 29 = 11111111111111111111111111111111 << 29 = 11100000000000000000000000000000
(前3
位为111
):
1
|
private static final int RUNNING = -1 << COUNT_BITS;
|
SHUTDOWN
状态 0 << 29 = 00000000000000000000000000000000 << 29 = 00000000000000000000000000000000
(前3
位为000
)
1
|
private static final int SHUTDOWN = 0 << COUNT_BITS;
|
STOP
状态 1 << 29 = 00000000000000000000000000000001 << 29 = 00100000000000000000000000000000
(前3
位为001
):
1
|
private static final int STOP = 1 << COUNT_BITS;
|
TIDYING
状态 2 << 29 = 00000000000000000000000000000010 << 29 = 01000000000000000000000000000000
(前3
位为010
):
1
|
private static final int TIDYING = 2 << COUNT_BITS;
|
TERMINATED
状态 3 << 29 = 00000000000000000000000000000011 << 29 = 01100000000000000000000000000000
(前3
位为011
):
1
|
private static final int TERMINATED = 3 << COUNT_BITS;
|
清楚状态位之后,下面是获得状态和线程数的内部方法:
1
|
// 得到线程数,也就是后29位的数字。 直接跟CAPACITY做一个与操作即可,CAPACITY就是的值就 1 << 29 - 1 = 00011111111111111111111111111111。 与操作的话前面3位肯定为0,相当于直接取后29位的值
|
线程池初始化状态线程数变量:
1
|
// 初始化状态和数量,状态为RUNNING,线程数为0
|
ThreadPoolExecutor
执行任务
使用ThreadPoolExecutor
执行任务的时候,可以使用execute
或submit
方法,submit
方法如下:
1
|
public Future<?> submit(Runnable task) {
|
很明显地看到,submit
方法内部使用了execute
方法,而且submit
方法是有返回值的。在调用execute
方法之前,使用FutureTask
包装一个Runnable
,这个FutureTask
就是返回值。
由于submit
方法内部调用execute
方法,所以execute
方法就是执行任务的方法,来看一下execute
方法,execute
方法内部分3个步骤进行处理。
- 如果当前正在执行的
Worker
数量比corePoolSize
(基本大小)要小。直接创建一个新的Worker
执行任务,会调用addWorker
方法 - 如果当前正在执行的
Worker
数量大于等于corePoolSize
(基本大小)。将任务放到阻塞队列里,如果阻塞队列没满并且状态是RUNNING
的话,直接丢到阻塞队列,否则执行第3
步。丢到阻塞队列之后,还需要再做一次验证(丢到阻塞队列之后可能另外一个线程关闭了线程池或者刚刚加入到队列的线程死了)。如果这个时候线程池不在RUNNING
状态,把刚刚丢入队列的任务remove
掉,调用reject
方法,否则查看Worker
数量,如果Worker
数量为0
,起一个新的Worker
去阻塞队列里拿任务执行 - 丢到阻塞失败的话,会调用
addWorker
方法尝试起一个新的Worker
去阻塞队列拿任务并执行任务,如果这个新的Worker
创建失败,调用reject
方法
上面说的Worker可以暂时理解为一个执行任务的线程。
execute
方法源码如下,上面提到的3
个步骤对应源码中的3个注释:
1
|
public void execute(Runnable command) {
|
addWorker
关系着如何起一个线程,再看addWorker
方法之前,先看一下ThreadPoolExecutor
的一个内部类Worker
, Worker
是一个AQS
的实现类(为何设计成一个AQS
在闲置Worker
里会说明),同时也是一个实现Runnable
的类,使用独占锁,它的构造函数只接受一个Runnable
参数,内部保存着这个Runnable
属性,还有一个thread
线程属性用于包装这个Runnable
(这个thread
属性使用ThreadFactory
构造,在构造函数内完成thread
线程的构造),另外还有一个completedTasks
计数器表示这个Worker
完成的任务数。Worker
类复写了run
方法,使用ThreadPoolExecutor
的runWorker
方法(在addWorker
方法里调用),直接启动Worker
的话,会调用ThreadPoolExecutor
的runWork
方法。需要特别注意的是这个Worker
是实现了Runnable
接口的,thread
线程属性使用ThreadFactory
构造Thread
的时候,构造的Thread
中使用的Runnable
其实就是Worker
。下面的Worker
的源码:
1
|
private final class Worker
|
接下来看一下addWorker
源码:
1
|
// 两个参数,firstTask表示需要跑的任务。boolean类型的core参数为true的话表示使用线程池的基本大小,为false使用线程池最大大小
|
Worker
中的线程start
的时候,调用Worker
本身run
方法,这个run
方法之前分析过,调用外部类ThreadPoolExecutor
的runWorker
方法,直接看runWorker
方法:
1
|
final void runWorker(Worker w) {
|
我们看一下getTask
方法是如何获得任务的:
1
|
// 如果发生了以下四件事中的任意一件,那么Worker需要被回收:
|
如果getTask
返回的是null
,那说明阻塞队列已经没有任务并且当前调用getTask的Worker
需要被回收,那么会调用processWorkerExit
方法进行回收:
1
|
private void processWorkerExit(Worker w, boolean completedAbruptly) {
|
在回收Worker的时候线程池会尝试结束自己的运行,tryTerminate
方法:
1
|
final void tryTerminate() {
|
解释了这么多,对线程池的启动并且执行任务做一个总结:
首先,构造线程池的时候,需要一些参数。
文章中的结尾已经说明了一下重要参数的意义。
线程池构造完毕之后,如果用户调用了execute
或者submit
方法的时候,最后都会使用execute
方法执行。
execute
方法内部分3
种情况处理任务:
- 如果当前正在执行的
Worker
数量比corePoolSize
(基本大小)要小。直接创建一个新的Worker
执行任务,会调用addWorker
方法 - 如果当前正在执行的
Worker
数量大于等于corePoolSize
(基本大小)。将任务放到阻塞队列里,如果阻塞队列没满并且状态是RUNNING
的话,直接丢到阻塞队列,否则执行第3
步 - 丢到阻塞失败的话,会调用
addWorker
方法尝试起一个新的Worker
去阻塞队列拿任务并执行任务,如果这个新的Worker
创建失败,调用reject
方法
线程池中的这个基本大小指的是Worker
的数量。一个Worker
是一个Runnable
的实现类,会被当做一个线程进行启动。Worker
内部带有一个Runnable
属性firstTask
,这个firstTask
可以为null
,为null
的话Worker
会去阻塞队列拿任务执行,否则会先执行这个任务,执行完毕之后再去阻塞队列继续拿任务执行。
所以说如果Worker
数量超过了基本大小,那么任务都会在阻塞队列里,当Worker
执行完了它的第一个任务之后,就会去阻塞队列里拿其他任务继续执行。
Worker
在执行的时候会根据一些参数进行调节,比如Worker
数量超过了线程池基本大小或者超时时间到了等因素,这个时候Worker
会被线程池回收,线程池会尽量保持内部的Worker
数量不超过基本大小。
另外Worker
执行任务的时候调用的是Runnable
的run
方法,而不是start
方法,调用了start
方法就相当于另外再起一个线程了。
Worker
在回收的时候会尝试终止线程池。尝试关闭线程池的时候,会检查是否还有Worker
在工作,检查线程池的状态,没问题的话会将状态过度到TIDYING
状态,之后调用terminated
方法,terminated
方法调用完成之后将线程池状态更新到TERMINATED
。
ThreadPoolExecutor
的关闭
线程池的启动过程分析好了之后,接下来看线程池的关闭操作:
shutdown
方法,关闭线程池,关闭之后阻塞队列里的任务不受影响,会继续被Worker
处理,但是新的任务不会被接受:
1
|
public void shutdown() {
|
interruptIdleWorkers
方法,注意,这个方法打断的是闲置Worker
,打断闲置Worker
之后,getTask
方法会返回null
,然后Worker
会被回收。那什么是闲置Worker
呢?
闲置Worker是这样解释的:
Worker
运行的时候会去阻塞队列拿数据(getTask
方法),拿的时候如果没有设置超时时间,那么会一直阻塞等待阻塞队列进数据,这样的Worker
就被称为闲置Worker
。由于Worker
也是一个AQS
,在runWorker
方法里会有一对lock
和unlock
操作,这对lock
操作是为了确保Worker
不是一个闲置Worker
。
所以Worker
被设计成一个AQS
是为了根据Worker
的锁来判断是否是闲置线程,是否可以被强制中断。
下面我们看下interruptIdleWorkers
方法:
1
|
// 调用他的一个重载方法,传入了参数false,表示要中断所有的正在运行的闲置Worker,如果为true表示只打断一个闲置Worker
|
shutdown
方法将线程池状态改成SHUTDOWN
,线程池还能继续处理阻塞队列里的任务,并且会回收一些闲置的Worker
。但是shutdownNow
方法不一样,它会把线程池状态改成STOP
状态,这样不会处理阻塞队列里的任务,也不会处理新的任务:
1
|
// shutdownNow方法会有返回值的,返回的是一个任务列表,而shutdown方法没有返回值
|
shutdownNow
的中断和shutdown
方法不一样,调用的是interruptWorkers
方法:
1
|
private void interruptWorkers() {
|
Worker
的interruptIfStarted
方法中断Worker
的执行:
1
|
void interruptIfStarted() {
|
线程池关闭总结:
线程池的关闭主要是两个方法,shutdown
和shutdownNow
方法。
shutdown
方法会更新状态到SHUTDOWN
,不会影响阻塞队列里任务的执行,但是不会执行新进来的任务。同时也会回收闲置的Worker
,闲置Worker
的定义上面已经说过了。
shutdownNow
方法会更新状态到STOP
,会影响阻塞队列的任务执行,也不会执行新进来的任务。同时会回收所有的Worker
。
原文作者:等一夏_81f7(简书作者)
原文标题:Java线程池ThreadPoolExecutor源码分析