深入理解ThreadPoolExecutor
一、概览
本文的全部分析基于JDK1.8,文章较长,请耐心阅读。
下面是本文所讲到的java线程池相关的API类图:
(一)Executor
Executor作为顶级接口,只有一个方法:
void execute(Runnable command);
execute()方法可能会在新的线程中执行,也有可能会在线程池中的某个线程中执行,也有可能直接在调用者线程中执行,这一切都依赖于Executor的具体实现。Executor其实可以理解成一个“任务管理器”,比起你直接创建Thread然后执行任务,它会替你完成线程的创建以及任务的执行,乃至线程的回收等等,当然这一切都依赖于Executor的具体实现。
(二)ExecutorService
ExecutorService接口继承了Executor,它是Executor接口重要的功能扩展,它抽象出了诸多方法,你大可把它当做真正的线程池接口,来看它的API截图:
(三)AbstractExecutorService
AbstractExecutorService是一个抽象类,它实现了ExecutorService接口,并且ExecutorService中定义的大多数方法都在该类中得到了实现,比如submit()的几个重载方法以及跟invoke相关的一些方法等等,来看它的API截图:
(四)ThreadPoolExecutor
ThreadPoolExecutor类是线程池中最核心的一个类,继承了AbstractExecutorService,它是线程池最具体的实现,因为它实现了Executor中所定义的execute()方法。对于该类的解析正是本文的重点。
(五)Executors
Executors是一个工具类,线程池的静态工厂,它已经为大多数使用场景预设了线程池,提供了快捷创建线程池的静态方法。在平时使用线程池的过程中,大多是用Executors来直接创建。
二、ThreadPoolExecutor-初步理解
(一)线程池的核心参数
ThreadPoolExecutor一共提供了四个public的构造方法,我们看它最重要的一个构造方法开始,因为其他三个最终都会调用到下面这个:
public class ThreadPoolExecutor extends AbstractExecutorService {
...
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
从构造方法着手的主要原因是为了去了解ThreadPoolExecutor里面的核心参数,下面一一讲解:
-
corePoolSize:线程池核心线程数
核心线程池大小描述的是通常情况下线程池中活跃的线程最小数量,一般来讲,一个线程池被创建后,它的初始线程个数是0。当有任务来的时候,就会创建线程执行任务,当池中的线程个数大于corePoolSize,就会将任务放入工作队列(workQueue)。
-
maximumPoolSize:线程池最大线程数
当阻塞队列也放不下任务的时候,就会继续创建线程来执行任务,但是最终该池中的线程个数不能超出maximumPoolSize。
-
keepAliveTime:线程保持活跃而不销毁的时间
一般来讲,keepAliveTime只会在线程个数超过corePoolSize的时候发挥作用,它会将线程保留keepAliveTime的时间,然后销毁,直到池中线程个数等于corePoolSize。但如果调用了allowCoreThreadTimeOut()方法,就相当于允许核心线程池中的线程也超时销毁,那么不管什么线程最终都会因为超出keepAliveTime时间而被销毁,直至最终线程个数为0。
-
unit:TimeUnit类型,作为keepAliveTime的时间单位
其实这是一个很有意思的工具类,也是自java 1.5加入到java.util.concurrent包内的,它封装了一些对当前线程的操作,比如线程sleep就推荐用TimeUnit去写,这里我们只需关注它的七个时间枚举:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
-
workQueue:工作队列
当池中线程个数大于corePoolSize的时候,任务就存放于此等待执行。workQueue类型为BlockingQueue,jdk已经内置了几种实现,通常会取以下几种类型:
- ArrayBlockingQueue:有界队列,基于数组的先进先出队列,此队列创建时,需要指定大小
- LinkedBlockingQueue:无界队列,基于链表的先进先出队列,如果创建时没有指定队列大小,则默认为Integer.MAX_VALUE。它其实间接的造成maximumPoolSize失效,同时也存在内存溢出的隐患
- SynchronousQueue:任务不用排队而是直接提交的队列,该队列下任务过来会直接新建线程执行
- PriorityBlockingQueue:和ArrayBlockingQueue差不多,不同的是它可以实现自动扩容,其中的元素要实现Comparatable接口,在创建时要传入comparator,这些都是用于元素的排序
-
threadFactory:线程工厂
线程工厂,顾名思义,就是用来生产线程的,它可以为线程设置一些属性,比如线程名、守护线程等,它还可以设置在主线程中对于子线程的未捕获异常的处理策略等等。一般来讲Executors中提供的默认线程工厂就足够了。
-
handler:表示拒绝执行任务的策略
它是RejectedExecutionHandler类型的引用,它会在队列饱和或者线程池被关闭时被调用。RejectedExecutionHandler接口在ThreadPoolExecutor中有四种类型的内部实现类:
- ThreadPoolExecutor.AbortPolicy(默认):丢弃任务,并抛出RejectedExecutionException异常
- ThreadPoolExecutor.CallerRunsPolicy:用调用者的线程执行任务
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中存在最久未被执行的任务,然后重新尝试execute执行,但如果线程池被关闭,任务会直接被丢弃
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,不会抛出异常
(二)线程池的执行流程
在上面的众多参数中,最需要理解的是corePoolSize和maximumPoolSize,可以做一个总结就是:
- 池中线程数小于corePoolSize,新任务过来会直接创建新线程来执行。
- 池中线程数大于等于corePoolSize,且workQueue未饱和,新任务过来会加入workQueue等待执行。
- 池中线程数大于等于corePoolSize,且workQueue已饱和,但线程数小于maximumPoolSize,新任务过来会创建新线程来执行。
- 池中线程数大于等于corePoolSize,且workQueue已饱和,并且线程数等于maximumPoolSize,新任务会被拒绝,拒绝策略就是handler。
所以,在我们接下来要探究线程池的细枝末节之前,能够大体了解它的执行流程是很有帮助的,正如上面总结的,线程池的执行大体就是在围绕corePoolSize和maximumPoolSize在进行,大家也可以参考下图来有个更直观的印象:
(三)线程池状态
先来看下面ThreadPoolExecutor中很重要的一段代码:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程数量占用的位数:32-3=29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 最大线程数,1*2的29次方再减1,足够大了
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
// 运行状态存储在高位(前三位),后面都是29个0
// 111(有符号)
private static final int RUNNING = -1 << COUNT_BITS;
// 000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001
private static final int STOP = 1 << COUNT_BITS;
// 010
private static final int TIDYING = 2 << COUNT_BITS;
// 011
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// 处理ctl的值,从中获取高3位得到线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 处理ctl的值,从中获取低29位得到线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 将runState(线程池状态)和workerCount(线程数量)通过 [按位或运算符] 合并成ctl的值
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* Bit field accessors that don't require unpacking ctl.
* These depend on the bit layout and on workerCount being never negative.
*/
// 下面的三个方法从名字上也能猜出它在干嘛
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
ctl是ThreadPoolExecutor的一个重要属性,它身兼两职,同时记录着线程池的线程数量和线程池状态。怎么做到的呢?从上面的代码可以看到,Integer.SIZE是32,COUNT_BITS是29,线程池状态均左移位29,所以通过ctl.get()获取的值,32位中,前三位用于表示线程池状态,后29位用于表示线程的数量。这样一来,一个变量就可以代表两种含义,这在很大程度上减少获取变量时锁的竞争。同时,AtomicInteger这个类是线程安全的,它可以通过CAS操作达到无锁并发(如果不清楚CAS是什么的,可以自行查阅资料)。线程池的状态一共有五种:
- RUNNING:运行状态,也是线程池的初始状态,会接收新任务并处理队列中的任务
- SHUTDOWN:停工状态,不再接收新任务,但正在执行的任务会继续执行,队列中的任务也会得到处理
- STOP:停止状态,不再接收新任务,正在执行的任务会被中断并且队列中的任务不会被处理
- TIDYING:清空状态,所有任务都停止,队列被清空,工作线程也全部销毁(workerCount=0),并且会接着调用terminated()方法
- TERMINATED:终止状态,terminated()方法调用后到此状态,此时线程池被销毁
三、ThreadPoolExecutor-任务提交
(一)execute()方法
接着想要深入理解ThreadPoolExecutor,就要来到一切的源头execute()方法。
向线程池提交任务有这2种方式,execute()方法和submit()方法。上面提到过submit()方法是ExecutorService接口中定义的,并且在AbstractExecutorService这个抽象类中就已得到实现。submit()方法主要是将任务封装成FutureTask的形式来提交,达到对任务进行监控的目的,比如中断任务的执行,获取执行结果等等,这一切的监控行为都是FutureTask自己维护的。submit()方法最终都会调用execute()方法,execute()方法是在ThreadPoolExecutor中实现的,来看下源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
/*
* 第一阶段:
* 如果线程数量 < 核心线程数量,就通过addWorker()新起线程执行任务,该线程占用corePoolSize个数
*/
if (workerCountOf(c) < corePoolSize) {
/*
* 如果addWorker()成功,则execute()返回
*/
if (addWorker(command, true))
return;
/*
* 如果addWorker()失败,则刷新ctl(ctl中保存了两个信息,线程池状态和线程个数,后面不再赘述)
* 失败原因可能是:
* 1.线程池状态是SHUTDOWN,此时不接收新任务
* 2.并发的原因,导致workerCountOf(c) >= corePoolSize
*/
c = ctl.get();
}
/*
* 第二阶段:
* 如果线程池是RUNNING状态,并且任务添加队列成功
*/
if (isRunning(c) && workQueue.offer(command)) {
/*
* 刷新ctl
*/
int recheck = ctl.get();
/*
* 双重校验线程池是否还是RUNNING状态,如果不是,则从队列中移除任务,并执行拒绝策略
*/
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 如果在上面双重校验线程池状态后,线程池依然是RUNNING状态,或者remove()失败,则检测线程数量是否为0
* 如果为0,则新起线程,此时线程不会有初始任务,并且占用maximumPoolSize个数
* 为什么要这样做呢?因为此时只需保证至少存在一个线程可以执行后续任务即可
* remove()失败的可能原因:刚好有线程执行完毕并且消耗了队列中的此任务
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 第三阶段:
* 如果线程池不是RUNNING状态,或者队列饱和,则尝试新起线程执行任务,并占用maximumPoolSize个数
* 如果此时新起线程仍然失败,则执行拒绝策略
* 新起线程仍然失败的可能原因:
* 1.线程池已经shutdown了
* 2.线程数量 > maximumPoolSize
*/
else if (!addWorker(command, false))
reject(command);
}
(二)执行流程图:
(三)小结:
execute()方法基本都是在对【线程数量】和【线程池状态】这两个要素进行管控,然后根据不同的管控结果进行addWorker()或者reject()操作,真正的添加线程并启动任务是在addWorker()中做的。所以,addWorker()方法是我们接下来要重点研究的。
四、ThreadPoolExecutor-添加线程
(一)addWorker()方法
来看源码:
private boolean addWorker(Runnable firstTask, boolean core) {
/*
* 外层循环,主要职责是从线程池状态上把关
* retry是一个标记,相当于goto的思想
*/
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/*
* 判断线程池状态的代码,这段代码反过来理解会轻松很多
* 首先,上面讲到的线程池状态一共四个:RUNNING=-1,SHUTDOWN=0,STOP=1,TIDYING=2,TERMINATED=3
* 数值越大,就越接近死亡状态
* 解析出来就是 A && B 都满足的时候,返回false,可以理解从【不可添加线程】状态。
* 反过来就是 !A || !B,只要是其中任意一种满足,就是【可添加线程】状态
* !A:只有一个状态,那就是RUNNING,只要是这个状态,就【可添加线程】
* !B:rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty(),什么场景呢?就是线程池
* 状态虽然为SHUTDOWN,但是没有初始任务,单纯为了起一个线程,为的就是执行队列中残存的任务。
* 只要满足这几个条件,也【可添加线程】
* 其他任何情况,都是直接返回false,【不可添加线程】
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
/*
* 内层循环,主要职责是从线程数量把关,并且【线程数量+1】
*/
for (;;) {
int wc = workerCountOf(c);
/*
* A || B,满足其中任意一种,就返回false,就是【不可添加线程】状态。
* A:wc >= CAPACITY,线程数量 > 线程数量统计容量上限,都爆表了自然【不可添加线程】
* B:wc >= (core ? corePoolSize : maximumPoolSize),线程数量的【临界值】,
* 是corePoolSize或者maximumPoolSize,取决于core这个参数,上面有讲到过。如果超出
* 这个【临界值】,此次添加线程也是返回false,就是【不可添加线程】
* 总结来说就是,不爆表,也不超出临界值,就【可添加线程】
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
/*
* CAS操作【线程数量+1】,成功则跳出外层循环
*/
if (compareAndIncrementWorkerCount(c))
break retry;
/*
* 失败则刷新ctl
*/
c = ctl.get(); // Re-read ctl
/*
* 然后判断线程池状态是否发生了变化
* 如果发生了变化,则继续外层循环
* 如果没有发生变化,则继续内层循环,执行CAS操作【线程数量+1】(CAS操作失败,
* 是因为workerCount发生改变,所以要继续判断内层循环)
*/
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 线程成功启动标志
boolean workerStarted = false;
// 添加worker线程成功标志
boolean workerAdded = false;
Worker w = null;
try {
/*
* Worker本身就是Runnable,目前大体只要知道会用ThreadFactory创建一个线程,
* 然后赋予worker的thread成员变量
*/
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
/*
* 获取线程池锁,并且上锁
*/
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
/*
* 对应线程池状态的双重检查,需要满足一下两点:
* 1.rs = RUNNING
* 2.rs == SHUTDOWN && 没有需要新接收的任务
*/
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
/*
* 检查线程是否已经被start过,确保每个线程只能start一次
*/
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
/*
* 将worker添加进workers
* workers本身是HashSet,非线程安全,所以要上锁操作
*/
workers.add(w);
int s = workers.size();
/*
* 刷新历史最大线程个数
*/
if (s > largestPoolSize)
largestPoolSize = s;
/*
* 更新标志
*/
workerAdded = true;
}
} finally {
/*
* 线程池解锁
*/
mainLock.unlock();
}
if (workerAdded) {
/*
* 在worker成功添加进workers后,线程启动并更新标志
*/
t.start();
workerStarted = true;
}
}
} finally {
/*
* 上面的很多因素都会导致addWorker失败,此时需要确保执行失败后的处理措施
*/
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
(二)参数解析:
- 第一个参数firstTask:表示新起线程并需要执行的初始任务,可以为空
- 第二个参数core:如果为true则表示占用corePoolSize个数,也就是线程数量会和corePoolSize进行比较,如果为false则表示占用maximumPoolSize个数,也就是线程数量会和maximumPoolSize进行比较。
(三)四种传参方式的使用场景:
在execute()方法中用到了前3种,最后一种其实也很重要,它可以帮助预加载全部的核心线程池,来看一下:
- addWorker(command, true):corePoolSize未满,此时就是在接收任务并新起线程执行它,所以在方法里面,线程数量会跟corePoolSize这个临界值比较
- addWorker(command, false):队列已饱和,此时也是在接收任务并新起线程执行它,但是不同的它尝试将线程扩容至maximumPoolSize,所以线程数量会跟corePoolSize这个临界值比较
- addWorker(null, false):此时是在做一种保护措施,新起一个线程但并不会给它分配初始任务,只是确保至少有一个线程消费队列中的任务。它的场景很可能是调用了shutdown(),这时候不会接受新任务,但队列中的任务依然会得到执行
- addWorker(null, true):它其实是在预启动核心线程池,这个方法新起的线程都没有初始任务,并且都占用corePoolSize个数,这些线程的职责就是等待从队列中获取任务并执行。实际使用中就是调用prestartAllCoreThreads()方法,这样就可以预先启动corePoolSize个线程,随时待命。
(四)执行流程图:
(五)小结:
addWorker()总体来说就是在添加并启动线程,只不过它会围绕【线程数量】和【线程池状态】做很多校验,以确保是【可添加线程】状态才会真正的添加线程。在设计上值得注意的是,对HashSet这个非线程安全类的操作是使用了重入锁的,并且只对最小最必要的代码块上锁,很大程度上减轻了并发时对锁的竞争。
接下来我们看看对线程的封装——Worker,Worker的设计也有很多的巧妙之处。
五、ThreadPoolExecutor-内部类Worker
/*
* Worker实现了Runnable,所以其本身就是任务,并且Worker继承了AQS,所以其本身就是锁
*/
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
// state初始赋值为-1,控制线程在启动后才能中断
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 将worker自己作为参数通过ThreadFactory创建线程
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
/*
* 真正的执行任务了
*/
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
/*
* 重写AQS的isHeldExclusively()
* 0代表【未锁定】状态
* 1代表【锁定】状态
*/
protected boolean isHeldExclusively() {
return getState() != 0;
}
/*
* 尝试获取锁
* 重写AQS的tryAcquire()
*/
protected boolean tryAcquire(int unused) {
/*
* 可以看到,这边每次都是用CAS操作,将状态从0->1,而不是递增,其实这已代表这是个【非重入锁】
*/
if (compareAndSetState(0, 1)) {
/*
* 设置当前线程为锁的owner,其实就是当前线程获取到锁
*/
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/*
* 尝试释放锁
* 重写AQS的tryRelease()
*/
protected boolean tryRelease(int unused) {
/*
* 释放锁,并且将锁状态设置为0(对比一下,锁在最开始的初始化状态为-1)
*/
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/*
* 以下四个public方法都是对AQS中方法的封装
*/
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
/*
* 顾名思义,只有启动后的线程才能被中断
* shutdownNow()方法最终会调用到该方法,因为shutdownNow不论你线程是否在运行,都会立即中断线程,
* 所以该方法中的中断不需要获取worker锁
*/
void interruptIfStarted() {
Thread t;
/*
* 可以看到state的状态只要求>0,所以在线程执行任务期间是可以中断的
*/
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
(一)Worker作为Runnable的作用
- Worker实现了Runnable接口,所以Worker本身就是任务。可以看Worker的构造函数,它将接收的任务赋值给firstTask成员变量,然后将自己作为参数传递给ThreadFactory线程工厂,这样创建出线程再赋值给thread变量。
- 继续看Worker的run()方法,很简单,就runWorker(this)一行代码,将自身作为参数传递。所以thread.start()启动线程后,执行的任务就是firstTask中的run(),这一点可以在接下来对runWorker()方法的讲解中得到验证。
(二)Worker作为AQS的作用
- Worker继承了AbstractQueuedSynchronizer(AQS——队列同步器),AQS是java中用来构建锁和其他并发组件的基础框架,它基于模板模式实现,其中没有任何abstract抽象方法,而是在需要子类实现的方法中都直接抛出UnsupportedOperationException异常。AQS有几个非常重要的成员变量,其中很重要的一个就是state,state代表的是锁的获取情况,对于state的操作都是CAS操作。(AQS还是值得学习一波的,有不清楚的可以自行查阅相关资料)
- 在Worker继承AQS时,它没有重写tryAcquireShared()和tryReleaseShared()方法,只是重写了tryAcquire()和tryRelease()方法,所以它是一个简单的独占锁。其实最重要的是它实现了非重入锁功能,大家可以看tryAcquire()方法中的注释。所以Worker通过继承AQS,就简化了线程在执行任务时对于锁的获取和释放,并且通过锁的获取状态控制线程中断。
(三)非重入锁设计必要性
tryAcquire()方法是获取worker锁的方法,它被tryLock()方法封装,所以谁调用了tryLock()方法:
/*
* 该方法主要就是中断空闲线程,何为空闲线程,就是不在执行任务的线程,反应到worker锁上就是
* 没有获取到锁的线程,即state=0的线程
*/
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
/*
* 获取worker锁,然后中断线程
*/
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
interruptIdleWorkers(boolean onlyOne)是ThreadPoolExecutor中封装的方法,只在这一处调用到了tryLock(),在ThreadPoolExecutor中,一共有以下方法调用了interruptIdleWorkers():
- setCorePoolSize()
- setMaximumPoolSize()
- setKeppAliveTime()
- allowCoreThreadTimeOut()
- shutdown()
此次对于非重入锁的必要性的分析,我们只以setCorePoolSize()为例,因为其他方法都与其类似。
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
/*
* 【核心线程个数】>【当前线程个数】时,会尝试中断空闲线程
*/
interruptIdleWorkers();
else if (delta > 0) {
// We don't really know how many new threads are "needed".
// As a heuristic, prestart enough new workers (up to new
// core size) to handle the current number of tasks in
// queue, but stop if queue becomes empty while doing so.
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
setCorePoolSize()方法主要是重新设置线程池的核心线程个数的,假如重新设置的【核心线程个数】>【当前线程个数】,那么就需要中断那些空闲线程。
那么何为空闲线程呢?线程在执行任务时,都会通过w.tryLock()获取worker锁,所以说获取到锁的线程就是工作中的线程,未获取到锁的线程就是空闲线程,这一点也可以在接下来对runWorker()方法的解析中得到验证。
反过来想,假如worker是可重入锁,那工作中的线程即使获取到worker锁也会被中断,因为该锁可重入,这样就达不到【只中断空闲线程】的目的。这也是shutdownNow()方法不在上面队列中的原因,shutdownNow()的调用会中断你所有线程,无论线程是否正在工作,所以shutdownNow()对线程的中断不需要获取worker锁,这一点在上面的interruptIfStarted()中也能得到验证。
这里强调一点,ThreadPoolExecutor有个成员变量mainLock,它是ReentrantLock重入锁,千万不要跟这里的worker锁搞混淆,它们的作用不一样。mainLock是线程池锁,在ThreadPoolExecutor的很多方法中都有用到,包括上面讲到的execute()和addWorker(),它主要是控制外部线程对线程池对象的安全调用。而worker锁主要是在对线程池内部线程做管理,控制它们的中断。
worker非重入锁的设计对于线程池中线程的中断控制可以做如下总结:
- worker对象刚new出来,锁的状态是state=-1,所以无论是interruptIfStarted()还是interruptIdleWorkers(),都无法中断线程。
- 对于setCorePoolSize(),setMaximumPoolSize(),setKeppAliveTime(),allowCoreThreadTimeOut()和shutdown(),它们最终会调用interruptIdleWorkers(),非重入锁的设计使得他们可以做到【只中断空闲线程】。
- 对于shutdownNow(),它最终会调用interruptIfStarted(),该方法不需要获取worker锁,所以它可以做到【立即中断所有线程】
继续线程池整个流程的分析,Worker的run()方法中调用了runWorker(this),这里面做了什么呢?
(四)执行任务:runWorker()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
/*
* 该方法最终会调用worker的tryRelease(),它会将worker的state设置为0,这样就具备了被中断的初始条件
*/
w.unlock(); // allow interrupts
/*
* 该值用于记录代码是否突然性完成,其实很好理解,它就是用来标志代码是否发生异常
* 它的值会影响在最后处理worker的退出时,需不需要新起worker来替换此时的这个worker
*/
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
/*
* 获取worker锁
*/
w.lock();
// If pool is stopping, ensure thread is interrupted
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
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 {
/*
* 任务置为空,worker完成任务数+1,worker锁解锁
*/
task = null;
w.completedTasks++;
w.unlock();
}
}
/*
* 只有从获取worker锁到释放worker锁整个期间,都没有出现异常,这个值才为false,因为只要有异常,
* 最后都会直接到下面的finally中
* false:代表整个过程没有异常
* true:代表过程中出现了异常
*/
completedAbruptly = false;
} finally {
/*
* 处理worker的退出
*/
processWorkerExit(w, completedAbruptly);
}
}
调用了runWorker()的线程才算真正启动,并且一个线程只启动这么一次。一个线程的诞生,或许为了执行新任务,或许为了执行队列中的任务,但无论如何,调用了runWorker()后,等待这个线程的要么是执行任务然后阻塞在getTask()上,如此往复,要么就是灭亡。
(五)小结:
整个runWorker()的代码读起来很顺畅,三个try()三个finally(),都有各自的作用,总结一下:
- worker锁先释放,这样state会被设置成0,此时就具备了线程被中断的初始条件了
- firstTask不为空,或者getTask()不为空,就会进入到执行task的run()方法的
- 执行任务之前,会先获取worker锁
- 开始执行task.run(),在前后会有自定义业务场景的执行,beforeExecute(wt, task)和afterExecute(task, thrown)
- 然后会释放worker锁
- 2-5的过程其实都在整个while循环中,所以就分7-8两种情况
- 假如任务执行过程中有任何异常,worker线程都会终止,processWorkerExit()会处理worker的退出流程,下面会有对processWorkerExit()方法的讲解
- 假如无任何异常,worker可能会在getTask()上阻塞,一直等取出任务,或者getTask()超时,超时就会返回null,这样就会结束while循环,也就意味着worker线程进入退出流程。下面就会进行getTask()的讲解。
六、ThreadPoolExecutor-任务获取
(一)getTask()方法
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.
/*
* 对于线程池状态的判断,满足其一就直接返回null
* 1.线程池状态 > SHUTDOWN会返回null
* 2.或者线程池状态 = SHUTDOWN的情况下,队列为空,也会直接返回null
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
/*
* 线程过期是否生效,满足其一即可
* allowCoreThreadTimeOut = true 或者 当前线程数量 > 核心线程池数量
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 对于线程过期并超时的判断:
* 意思是说,只要队列是空或者线程数不止一个,那么线程超时应该立马得到反馈
* 反馈的结果就是线程数减一并返回null
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 会根据是否允许线程生效,来调用队列的不同方法
* 1.生效:调用poll(),poll会阻塞keepAliveTime时长,超时后还没有获取到任务,
* 就会返回null,之后timedOut设置为true,会在下次循环中因线程超时退出
* 2.不生效:调用take(),take在队列中没有任务时会一直阻塞
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
/*
* 线程如果被中断,会重新开始整个循环
*/
timedOut = false;
}
}
}
(二)执行流程图:
(三 )小结:
-
获取ctl
-
进行线程池状态的判断,判断规则:
- 线程池状态为SHUTDOWN,并且队列为空,则【线程数-1】然后返回null
- 或者线程池状态>SHUTDOWN,也同样可以【线程数-1】然后返回null
这是很有必要的,SHUTDOWN状态是可以继续执行队列中的任务的,如果>SHUTDOWN了,那么方法肯定要尽快返回。
-
是否允许线程过期生效,判断规则:
- 如果设置过了allowCoreThreadTimeOut=true,那么timed=true,即线程过期生效
- 如果当前线程数 > 核心线程数,那么也生效
-
线程是否超时,如果满足,则CAS【线程数-1】并返回null,如若CAS操作是吧,则重新开始循环,判断规则:
- 线程数 > maximumPoolSize或者线程过期超时
- 同时还有满足,线程数 > 1或者队列为空
-
会根据timed值来调用队列不同的任务获取方法:
- true:调用poll(),poll会阻塞keepAliveTime时长,超时后还没有获取到任务,就会返回null,之后timedOut设置为true,会在下次循环中因线程超时退出
- false:调用take(),该方法会一直阻塞直到获取任务
-
获取任务的整个过程,如若线程被中断,会重置timedOut = false,然后重新整个循环
七、ThreadPoolExecutor-线程退出:
(一 )processWorkerExit()方法
在讲runWorker()方法的时候有说过,worker会因为执行异常或者getTask()超时而进入worker的退出流程,下面来看看这个退出流程做了什么,源码:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
/*
* 如果是runWorker()中发生异常,这边就要【线程数-1】
*/
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
/*
* 将该worker处理的任务总数计入到线程池处理的任务总数中去,
* 然后从集合中移除该worker(代表该worker的终结)
*/
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
/*
* 这是一种【尝试终止线程池】的操作
* 在整个ThreadPoolExecutor中,有好几个方法都有调用到它,其实它包含一种含义,
* 就是对线程池有趋于“死亡”操作的方法都会【尝试终止线程池】,比如shutdown()
*/
tryTerminate();
int c = ctl.get();
/*
* 下面都在讨论是否需要新起worker来代替该worker
* 一切的前提是线程池的状态 < STOP,即RUNNING或者SHUTDOWN
*/
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);
}
}
(二)小结:
- 如果是runWorker()中发生异常,需要执行【线程数-1】操作
- 线程池上锁
- 将worker处理过的任务计入到线程池处理的任务总数中去,然后从集合中移除该worker(代表该worker的终结)
- tryTerminate()是一种【尝试终止线程池】的操作,在整个ThreadPoolExecutor中,有好几个方法都有调用到它,它的含义就是,任何对线程池有趋于“死亡”操作的方法都会【尝试终止线程池】,比如shutdown()
- 判断是否需要新起worker来代替该worker,判断逻辑大体为:
- 首先必须满足线程池的状态 < STOP,即RUNNING或者SHUTDOWN
- 如果是runWorker()中发生异常,就addWorker
- 如果runWorker()中没有发生异常,但是判断出“目前的线程数量偏少”了,就addWorker。“目前的线程数量偏少“的判断是很有意思的,它的核心思想就是【维护核心线程池】或者【队列不空,至少维护存在一个线程】
八、总结:
本篇文章主要是对线程池原理的剖析,由于篇幅的原因,对于Executors静态工厂提供的几种创建线程池的方法并未提及。
然后,对于线程池中如何终止线程池这一话题,也只在“非重入锁设计必要性”中稍有讲到,这一部分内容其实很有必要单独拎出来进行讲解,里面有很多Doug Lea大神的思想精华。

浙公网安备 33010602011771号