关于线程池的文章很多,有的充斥着理论和各种专业名词,很容易让人犯晕、搞混概念,降低理解线程池的效率。有的写得很详细很好,但忽略了一些地方。于是我索性抛开这些文章,直接深入源码,重新抽丝剥茧,不过难免犯错走进代码逻辑的死胡同,再翻看别人的好文章才得以走通。现在把总结写出来,尽量用源码(jdk 1.8)说话,用简单的语言说通概念和逻辑,作为别人文章的一种补充。如有错误或不足,欢迎指出。
线程池的好处
线程池其中一个好处是什么?
- 线程复用,减少系统开销。
众所周知,创建一个线程系统开销大。假设有以下代表任务的类
class MyTask implements Runnable{
public void run(){
//...
};
}
如果要创建10个该任务并行执行,就要创建10个线程。
for(int i=0;i<10;i++){
Thread t = new Thread(new MyTask());
t.start();
}
如果用了线程池,只需这样。假设使用具有固定线程的线程池,且只有5个线程,执行10个任务,比上例系统开销少一半,平均一个线程执行2个任务,这就是线程复用。
ExecutorService es = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
es.execute(new MyTask());
}
es.shutdown();
线程池的种类和概念
线程池的种类
通过java.util.concurrent.Executors创建不同类型的线程池:
- Executors.newCachedThreadPool()
创建无界线程池,动态地根据任务数和处理能力新增或回收线程。任务统统被放在阻塞队列里排队,每个线程都尝试从阻塞队列里取出任务,有任务就执行它。当队列没有任务时,线程阻塞60秒,60秒后依然没有任务,回收线程。当每个线程都繁忙,队列还有任务在等待,就再创建一个线程执行它。
- Executors.newFixedThreadPool( int n)
创建有界的线程池,线程池里只有n个线程,线程处理完一个任务就从阻塞队列里取出其他任务。当所有线程都在忙,其他任务在队列里等待。
- Executors.newSingleThreadExecutor()
创建单线程的线程池。
- Executors.newScheduledThreadPool( int n)
(因为这个线程池是另一个线程池类,所以不在本文讨论之列,详细可看其他文章。)
从Executors源码可以看到,前三个方法创建的都是一样的线程池类ThreadPoolExecutor,只是参数不一样,本文只说这个类。
看ThreadPoolExecutor的构造方法和其属性,稍后逐个说明:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//...(省略部分参数检查源码)
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor的内部类Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
//...
}
首先Worker实现了Runnable接口。线程池中创建的线程,都是Worker线程,每个Worker线程通过直接调用任务实例的run方法,以实现对任务的执行。
另外Worker还继承了AbstractQueuedSynchronizer(后面简称AQS),使得Worker获得锁功能。为什么要锁功能?稍后再说,预告一下,就是shutdown方法和shutdownNow方法实现原理的区别。
PS:ReentranLock之所以能实现代码块的并发访问控制,就是因为继承了AQS而实现的(忘了是从JDK哪个版本开始,ReentranLock类抛开对其的依赖,而是通过内部类ReentrantLock.Sync来继承),而不是通过关键字synchronized等方式实现,可见其强大之处。以后有机会,我会去研究一下AQS,有结果就再写一篇文章。
ThreadPoolExecutor的重要属性
- corePoolSize
直译为核心线程池大小,使用FixedThreadPool时才用到,用于限制创建Worker线程的数量。例如Executors.newFixedThreadPool(5),corePoolSize就是5,只能创建5个Worker线程。
- maximumPoolSize
直译为最大线程池大小,使用CachedThreadPool时才用到,默认值为Integer.MAX_VALUE,也用于限制创建Worker线程的数量,当然MAX_VALUE个线程只是极端情况,毕竟CachedThreadPool是按需创建或销毁线程。当使用FixedThreadPool时,此值等于corePoolSize。
- keepAliveTime
Worker线程等待任务的时间,使用CachedThreadPool时此值默认为60秒,表示当阻塞队列没有任务时,线程等待60秒再取任务,还是没有的话,Worker线程被回收。FixedThreadPool该值为0
- workQueue
它是一个无界LinkedBlockingQueue阻塞队列,任务在此排队等待被Worker获取和执行。队列为空时,Worker线程对队列的take/poll操作会阻塞进入等待状态。(我觉得命名为taskQueue会不会更好一点,毕竟它放的是任务。)
- allowCoreThreadTimeOut
决定Worker线程如何从workQueue取出任务的行为。默认为false,当workQueue没有任务,Worker线程阻塞等待,直到该队列有任务为止。设为true,当workQueue没有任务,Worker线程只阻塞keepAliveTime时间,还是没有任务就返回null。
- RejectExecutionHandler
拒绝策略,默认为AbortPolicy
- ThreadFactory
创建Worker线程的线程工厂类,默认为Executors.DefaultThreadFactory
- workers
Worker实例的集合,是个普通的HashSet。每次对workers的增删操作,有ReentranLock保证并发安全。
- ctl
这个参数设计很巧妙,既用于记录Worker的数量,也用于记录线程池状态(生命周期)。
ctl属性
这个属性十分重要,看源码:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
其中静态参数RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED表示线程池的各种状态,由类型为AtomInteger的ctl记录当前线程池状态,保证对线程池状态的修改都是原子操作。ctl这个属性设计十分巧妙,我觉得是属于大牛级别的技术,可以借鉴应用到其他项目中,下面开始解释。
ctl有两个作用:
- 记录线程池状态
- 记录Worker数量,包括活动的和空闲的(或叫阻塞)。
线程池状态有:
- RUNNING 线程池可用,可以通过execute方法添加新任务
- SHUTDOWN 线程池已关闭,无法通过execute方法添加新任务,但已经排队的任务或正在执行的任务不受影响
- STOP 线程池已停止,既无法通过execute方法添加新任务,在排队或正在执行的任务也要立即中断
- TIDYING 线程池已经没有任务存在了,Worker数量也为0,可以终结线程池
- TERMIATED 线程池已终结
奇怪,一个Integer,取值范围是-2^31 到 2^31-1,仅且只能有一个数值,如何既记录线程池状态,又记录Worker数量呢?二进制的厉害之处就在这里了,看下表列出了各状态的值:
属性 | 十进制 | 二进制 | 十六进制 |
CAPACITY | 536870911 | 0001 1111 1111 1111 1111 1111 1111 1111 | 0x1FFFFFFF |
RUNNING | -536870912 | 1110 0000 0000 0000 0000 0000 0000 0000 | 0xE0000000 |
SHUTDOWN | 0 | 0000 0000 0000 0000 0000 0000 0000 0000 | 0x00000000 |
STOP | 536870912 | 0010 0000 0000 0000 0000 0000 0000 0000 | 0x20000000 |
TIDYING | 1073741824 | 0100 0000 0000 0000 0000 0000 0000 0000 | 0x40000000 |
TERMINATED | 1610612736 | 0110 0000 0000 0000 0000 0000 0000 0000 | 0x60000000 |
线程池利用ctl的二进制数据的前3位记录状态,后29位记录Worker数量。
例如在RUNNING状态,有1个Worker时,ctl就是:
1110 0000 0000 0000 0000 0000 0000 0001,十进制即 -536870911。前3位111是RUNNING,后29位就是1的二进制。
RUNNING状态,有2个Worker时,ctl就是:
1110 0000 0000 0000 0000 0000 0000 0010,十进制即 -536870910,后29位是2的二进制
RUNNING状态,Worker最多时,ctl就是:
1111 1111 1111 1111 1111 1111 1111 1111,十进制即 -1,后29位是536870911的二进制
要如何从ctl中取得状态值和Worker数呢,通过“按位与”操作即可,看源码:
private static int runStateOf(int ctl){ //根据ctl的值取得状态
return ctl & ~CAPACITY;
}
private static int workerCountOf(int ctl){ //根据ctl的值取得Worker数
return ctl & CAPACITY;
}
又如何在ctl中设置状态值和Worker数呢?通过“按位或”操作即可,看源码:
private static int ctlOf(int rs, int wc) { // rs是状态,wc是Worker数
return rs | wc;
}
所以,ctl的初始值表示线程池状态是RUNNING,Worker数为0。
关于“按位或”在现实中的应用,可以参考这篇别人的文章《按位或在多选中的应用》
线程池状态的变化看下图,可以看到是单向的,参照上面的表格,线程池状态值只能变大:
线程池工作流程及原理
之前已经提到,不管是Executors.newFixedThreadPool、Executors.newCacherThreadPool还是Executors.newSingleThreadExecutor,创建的对象都是ThreadPoolExecutor,只是构造方法的参数不同。现在以FixedThreadPool为例,分析其源码。
假设该线程池只有2个线程,放入3个任务(假设这3个任务都比较耗时),然后shutdown线程池:
ExecutorService es = Executors.newFixedThreadPool(2);
es.execute(new MyTask("Task_1"));
es.execute(new MyTask("Task_2"));
es.execute(new MyTask("Task_3"));
es.shutdown();
该线程池工作基本流程:
1. 创建线程数为2的有界线程池,初始状态为RUNNING。
2. 线程池处于RUNNING状态时,可以通过execute方法往线程池添加任务。
3. 假设每个任务都比较耗时,前2个任务分别给2个Worker线程执行,其余任务在workQueue里排队。
4. 关闭线程池,无法再通过execute方法添加新任务
a) 假设调用shutdown方法关闭线程池,虽然不能添加新任务,但正在执行的任务和排队的任务不受影响,此时线程池状态为SHUTDOWN。
b) 假设调用shutdownNow方法关闭线程池,不但无法添加新任务,而且正在执行的任务和排队的任务都被终止,此时线程池状态为STOP。
5. 当一个Worker线程完成一个任务,就立即从workQueue里取出其他任务。当workQueue已经没有任务,此Worker线程处于空闲状态(阻塞),直到workQueue再有任务时才继续执行。
6. 当线程池处于SHUTDOWN状态时,workQueue没有任务,其中一个刚执行完最后一个任务的Worker线程先调用tryTerminate()尝试终结其他空闲的Worker线程,再终结自己。
7. 当所有Worker线程已经终结,线程池状态设为TIDYING,线程池跟着终结,状态最后变为TERMINATED。
execute方法流程图
这个流程图忽略了部分不那么重要的细节,只展示重要的工作流程和逻辑。稍后的源码分析也分开Main线程和Worker线程来分析,我觉得这样比较好理解多线程下的流程。
Main线程,execute方法
线程池就通过这个方法添加任务Task
public void execute(Runnable task){
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(task, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(task)){
//...
}
}
exeucute方法很简单,先通过ctl取得Worker数(包括繁忙和空闲的),小于corePoolSize就调用addWorker方法创建Worker实例和Worker线程,执行传入的任务Task。大于corePoolSize就将任务Task放入workQueue排队。如果线程池状态不是RUNNING了,就拒绝任务Task。
Main线程,addWorker方法
addWorker用于创建Worker实例,并负责启动Worker线程。分两部分,前一部分通过两个for循环来检测是否足够条件执行addWorker。
private boolean addWorker(Runnable firstTask, boolean core){
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 当初我就在这里犯了低级错误,搞错了逻辑,导致之后对流程的理解进入了死循环。当满足这些条件时,返回fasle,拒绝加入任务Task,条件有:
// 1. 状态为STOP、DIDYING或TERMINATE
// 2. SHUTDOWN时firstTask为null
// 3. SHUTDOWN时workQueue为空
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
return false;
for (;;) {
// 这里只判断Worker数是否超过线程池容许的线程数量,超过肯定拒绝加入任务
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 如果以上拒绝条件都没有,就给ctl的Worker数量+1,并跳出循环,进入addWorker的下一部分
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
//...
}
addWorker方法的第二部分,忽略了try代码和判断代码
Worker w = new Worker(firstTask);
final Thread t = w.thread;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)){
workers.add(w); //workers只是个HashSet,由mainLock保证并发安全
}
mainLock.unlock();
t.start(); //启动Worker线程
创建Worker实例,Worker类继承了AQS实现了Runnable接口,其构造方法也很简单
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
Worker(Runnable firstTask) {
setState(-1); //设置AQS的state状态,0表示未锁定,1表示锁定
this.firstTask = firstTask; //Worker维护了任务Task
this.thread = getThreadFactory().newThread(this); //创建Worker线程
}
//...
}
初始化Worker时为什么state为-1而不是0,后面会解释。
addWorker方法成功,返回true,并结束execute方法。
当Main线程执行完三次execute方法添加了三个任务Task后(假设每个任务都比较耗时),线程池状态如下。此时还有2个Worker线程,正在执行任务Task_1和Task_2,Task_3在排队。
Main线程,shutdown方法
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN); //设置ctl的状态为SHUTDOWN
interruptIdleWorkers(); //中断空闲的Worker线程,稍后再详细分析
onShutdown(); //触发shutdown事件,默认啥都没做
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试终结线程池
}
由于目前仍有Worker线程在执行任务,在此条件下shutdown方法也仅仅是将线程池状态设为SHUTDOWN,其他行为稍后分析。
此时,Main线程的步骤已经走完,开始看另外两个Worker线程到底在干嘛。
Worker线程,runWorker方法
先看看runWorker的简单流程图,依然忽略了一些不重要的细节
众所周知,当Worker线程调用start方法启动线程,实则调用Worker实例的run方法,看源码,它直接调用ThreadPoolExecutor的runWorker方法
public void run() {
runWorker(this); //this是Worker实例
}
省略了部分无关紧要的runWorker代码
final void runWorker(Worker w){
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // a)
boolean completedAbruptly = true;
//从Worker里取出任务Task,如果为null就通过getTask方法从workQueue里取,都是null就结束,让当前Worker进入销毁阶段
while (task != null || (task = getTask()) != null){
w.lock(); // b)
//当线程池处于STOP状态,线程中断自己
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
task.run(); //直接调用任务Task的run方法,执行任务本身
task = null; //重置task引用,目的是执行完一个任务,再执行workQueue里的另一个任务
w.unlock(); // c)
completedAbruptly = false;
}
//当连getTask也返回null表示没有任何任务,可以销毁当前Worker实例
processWorkerExit(w, completedAbruptly);
}
上面的代码注释不难理解,然而a)、b)和c)步骤是什么鬼呢?有什么用呢?这涉及到Worker线程的中断机制:
a) 通过unlock方法给Worker实例解锁(AQS的state设为1),此时Worker线程还没开始访问任务Task,表示Worker线程可以被其他线程中断
b) 在while代码块里,表示任务Task已经准备就绪,通过lock方法给Worker实例加锁,表示Worker线程开始执行任务,不能被其他Worker线程中断,只能在线程池处于STOP状态时自己中断自己(回顾之前STOP的描述),或被Main线程强制中断。
c) 任务Task已经顺利完成,解锁Worker实例(AQS的state设为0)。
也就是说,当Worker线程在执行任务时,Worker处于lock状态。当完成任务或任务没有就绪,Worker处于unlock状态。
为什么其他Worker线程要取得别的Worker的锁时才能中断它的线程。Main线程则不要取得锁就可以中断别的Worker线程,个中实现原理在后面介绍interruptIdleWorkers方法中会说到。
Worker线程,getTask方法
假设此时任务Task_2已经完成,Woker#2实例就访问workQueue,发现有任务Task_3,就取出并执行。
如何从workQueue取出任务Task_3的呢,看getTask方法源码。
private Runnable getTask(){
for (;;){
int c = ctl.get();
int rs = runStateOf(c);
// 当满足这些条件时,终止getTask方法,返回null,并将ctl的Worker数量减一。表示当前Worker已经没有任务可以执行,进入销毁步骤
// 1. 线程池是STOP、TIDYING或TERMINATE时
// 2. SHUTDOWN时,workQueue为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//使用FixedThreadPool且allowCoreThreadPool为fasle,此为false
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果Worker数超过线程池极限,当然不能再增加活动的Worker。或者poll超时,当前Worker自然也要终结,返回null进入销毁步骤
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
//从阻塞队列取出任务Task,如果allowCoreThreadPool为true就用poll方法,否则take方法
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true; //记录poll超时
}
}
阻塞队列的poll方法和take方法有什么区别呢?
- poll方法从阻塞队列中取出对象,如果队列为空,则当前线程阻塞keepAliveTime时间再尝试取出,还是没有就返回null,记录超时状态,在重新进入for循环时才试图终结Worker。
- take方法是,如果队列为空,当前线程则一直阻塞,直到队列有对象为止,返回该对象。
所以当使用FixedThreadPool,都是take方法,Worker线程有可能在这里出现阻塞。使用CachedThreadPool且allowCoreThreadTimeOut为true,则是poll方法。
现在假设这么一个情况,Worker#2线程在getTask方法第一段代码workQueue.isEmpty()时,Task_3还是在的。但中途线程调度器切换到Worker#1线程,并处理完之前的任务,并workQueue.take()取出了Task_3任务执行。线程调度器切换回Worker#2线程,当来到workQueue.take()时,阻塞队列已经没有对象,Worker#2线程阻塞,进入空闲状态。
Worker#1线程,runWorker方法
现在Worker#2线程处于阻塞状态,只剩Worker#1线程在运行了。
while (task != null || (task = getTask()) != null){
w.lock();
task.run(); //执行任务Task_3
task = null;
w.unlock();
}
processWorkerExit(w, completedAbruptly);
当执行完任务Task_3之后,再次调用getTask方法,workQueue为空,返回null,结束while循环,调用processWorkerExit方法,Worker#1开始销毁步骤。
要清楚一点,此时Worker#2实例是没有锁的,没有锁的,没有锁的。因为lock方法在getTask方法之后,而Worker#2在getTask方法中处于阻塞状态,所以Worker#2实例是处于unlock状态,这十分重要,后面会解释。
Worker#1线程,processWorkerExit方法
当Worker没有任务可以处理,进入自销毁阶段。
省略了部分不重要的代码段
private void processWorkerExit(Worker w, boolean completedAbruptly){
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks; //线程池总的执行任务数
workers.remove(w); //从workers删除一个Worker实例
} finally {
mainLock.unlock();
}
tryTerminate();
//...
addWorker(null, false);
}
最后的addWorker(null)是什么意思呢,就是在RUNNING或SHUTDOWN且workQueue非空时,再创建一个Worker处理workQueue里余下的任务。通常CacherThreadPool才有这一步,意味着所有Worker都繁忙,刚好在processWorkerExit方法执行中又有新任务加入,线程池动态新创建一个Worker进行处理。
如果是FixedThreadPool,addWorker(null)返回false,不做任何事。
Worker#1线程,tryTerminate方法
尝试终结线程池,将状态设为TIDYING,最后设为TERMINATED
简单流程图如下
终止tryTerminate方法的条件有:
- 线程池状态是RUNNING时
- 线程池状态是SHUTDOWN时workerQueue非空
- 线程池状态已经是TIDYING或TERMINATE时
- ctl的Worker数不为0
ctl的Worker数不是0,意味着还有Worker线程在执行任务,或有Worker线程空闲(阻塞),就调用interruptIdleWorkers(ONLY_ONE)中断一个空闲(阻塞)的Worker线程,并结束。
int c = ctl.get();
if (isRunning(c) || runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE); // 中断一个空闲(阻塞)的Worker线程
return;
}
worker数为0,表示已经没有任何任务,也没有任何Worker,可以将线程池状态设为TIDYING,然后再设为TERMINATED。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated(); //空方法
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll(); //唤醒其他线程
}
return;
}
Worker#1线程,interruptIdleWorkers方法
负责中断一个空闲的Worker线程。分析这个方法之前,先看看shutdown方法和shutdownNow方法有什么不一样。
shutdown方法的代码片段:
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers(); //看这里
onShutdown();
shutdownNow方法的代码片段:
List<Runnable> tasks;
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers(); //看这里
tasks = drainQueue();
区别就是:
- shutdown方法调用interruptIdleWorkers方法
- shutdownNow方法调用interruptWorkers方法
再看看interruptIdleWorkers方法和interruptWorkers方法有什么不一样
interruptIdleWorkers方法的代码片段:
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) { //尝试取得Worker的锁
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
interruptWorkers方法的代码片段:
for (Worker w : workers)
w.interruptIfStarted();
可以看到,interruptIdleWorkers方法在尝试中断其他线程时,会先对Worker#2调用tryLock方法尝试取得Worker#2的锁。我还没有研究AQS的源码,但我已经猜测到,当Worker#1线程尝试取得Worker#2的锁时,如果无法取得(代表Worker#2正在执行任务),则Worker#1线程在此阻塞。如果取得,Worker#1线程就中断Worker#2线程,那么Worker#2线程抛出InterruptedException。
interruptWorkers方法就不一样,它不会尝试取得Worker锁,它不管Worker是否有锁,立即中断该线程。看interruptIfStarted方法的源码:
Thread t;
//AQS的state为0表示未锁定,1表示锁定。Worker实例初始化时state为-1,表示我还没开始工作做贡献实现价值呢,你不要中断我的。
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted())
t.interrupt();
纵观ThreadPoolExecutor所有代码,只有shutdownNow方法调用interruptWorkers方法。
所以,这就是Worker加锁时(正在执行任务)不能被其他Worker线程中断的原理。而当Main线程强制调用shutdownNow方法时,Worker无论是否有锁,也能被Main线程中断。
至此,Worker#1线程的interruptIdleWorkers方法中断了Worker#2线程,Worker#2线程抛出InterruptedException,(回顾getTask方法的代码)。
Worker#1线程也已完结
完结
最后贴出Executors.newCachedThreadPool()的基本流程,依然省略了不那么重要的步骤。
如果想了解详细的流程,可以参考别人的文章: