1.线程池的作用
一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时候,每当需要执行异步任务时候是直接 new 一线程进行运行,而线程的创建和销毁是需要开销的。使用线程池时候,线程池里面的线程是可复用的,不会每次执行异步任务时候都重新创建和销毁线程。
另一方面线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等,每个 ThreadPoolExecutor
也保留了一些基本的统计数据,比如当前线程池完成的任务数目等。
2.ThreadPoolExecutor 原理探究
类图如下:
如上类图,Executors 其实是个工具类,里面提供了好多静态方法,根据用户选择返回不同的线程池实例。
ThreadPoolExecutor
继承了 AbstractExecutorService
,成员变量 ctl 是个 Integer 的原子变量用来记录线程池状态 和 线程池中线程个数,类似于 ReentrantReadWriteLock
使用一个变量存放两种信息。
这里假设 Integer 类型是 32 位二进制标示,则其中高 3 位用来表示线程池状态,后面 29 位用来记录线程池线程个数。
//用来标记线程池状态(高3位),线程个数(低29位)
//默认是RUNNING状态,线程个数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程个数掩码位数,并不是所有平台int类型是32位,所以准确说是具体平台下Integer的二进制位数-3后的剩余位数才是线程的个数,
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程最大个数(低29位)00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程池状态:
//(高3位):11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//(高3位):00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//(高3位):00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//(高3位):01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//(高3位):01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取高三位 运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取低29位 线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
//计算ctl新值,线程状态 与 线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池状态含义:
-
RUNNING:接受新任务并且处理阻塞队列里的任务;
-
SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务;
-
STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务;
-
TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用
terminated
方法; -
TERMINATED:终止状态,terminated方法调用完成以后的状态。
线程池状态转换:
1.RUNNING -> SHUTDOWN:显式调用 shutdown()
方法,或者隐式调用了 finalize()
,它里面调用了 shutdown()
方法。
2.RUNNING or SHUTDOWN -> STOP:显式调用 shutdownNow()
方法时候。
3.SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候。
4.STOP -> TIDYING:当线程池为空的时候。
5.TIDYING -> TERMINATED:当 terminated() hook
方法执行完成时候。
线程池参数:
-
corePoolSize:线程池核心线程个数;
-
workQueue:用于保存等待执行的任务的阻塞队列;比如基于数组的有界
ArrayBlockingQueue
,基于链表的无界LinkedBlockingQueue
,最多只有一个元素的同步队列SynchronousQueue
,优先级队列PriorityBlockingQueue
等。
-
maximunPoolSize:线程池最大线程数量。
-
ThreadFactory:创建线程的工厂。
-
RejectedExecutionHandler:饱和策略,当队列满了并且线程个数达到
maximunPoolSize
后采取的策略,比如AbortPolicy
(抛出异常),CallerRunsPolicy
(使用调用者所在线程来运行任务),DiscardOldestPolicy
(调用 poll 丢弃一个任务,执行当前任务),DiscardPolicy
(默默丢弃,不抛出异常)。 -
keeyAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量要多,并且是闲置状态的话,这些闲置的线程能存活的最大时间。
-
TimeUnit,存活时间的时间单位。
-
-
线程池类型:
1.newFixedThreadPool:创建一个核心线程个数和最大线程个数都为 nThreads 的线程池,并且阻塞队列长度为
Integer.MAX_VALUE
,keeyAliveTime=0
说明只要线程个数比核心线程个数多并且当前空闲则回收。代码如下:public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //使用自定义线程创建工厂 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
2.newSingleThreadExecutor:创建一个核心线程个数和最大线程个数都为1的线程池,并且阻塞队列长度为
Integer.MAX_VALUE
,keeyAliveTime=0
说明只要线程个数比核心线程个数多并且当前空闲则回收。代码如下:public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } //使用自己的线程工厂 public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
3.newCachedThreadPool:创建一个按需创建线程的线程池,初始线程个数为 0,最多线程个数为
Integer.MAX_VALUE
,并且阻塞队列为同步队列,keeyAliveTime=60
说明只要当前线程 60s 内空闲则回收。这个特殊在于加入到同步队列的任务会被马上被执行,同步队列里面最多只有一个任务。代码如下:public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } //使用自定义的线程工厂 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
如类图,其中 mainLock 是独占锁,用来控制新增 Worker 线程时候的原子性,termination 是该锁对应的条件队列,在线程调用
awaitTermination
时候用来存放阻塞的线程。Worker 继承 AQS 和 Runnable 接口,是具体承载任务的对象,Worker 继承了 AQS,自己实现了简单不可重入独占锁,其中
status=0
标示锁未被获取状态,state=1
标示锁已经被获取的状态,state=-1
是创建 Worker 时候默认的状态,创建时候状态设置为 -1 是为了避免在该线程在运行runWorker()
方法前被中断,下面会具体讲解到。其中变量 firstTask 记录该工作线程执行的第一个任务,thread 是具体执行任务的线程。DefaultThreadFactory
是线程工厂,newThread
方法是对线程的一个修饰,其中poolNumber
是个静态的原子变量,用来统计线程工厂的个数,threadNumber
用来记录每个线程工厂创建了多少线程,这两个值也作为线程池和线程的名称的一部分。3.源码分析
1 public void execute(Runnable command):
execute
方法是提交任务 command 到线程池进行执行,用户线程提交任务到线程池的模型图如下所示:如上图可知
ThreadPoolExecutor
的实现实际是一个生产消费模型,其中当用户添加任务到线程池时候相当于生产者生产元素,workers 线程工作集中的线程直接执行任务或者从任务队列里面获取任务相当于消费者消费元素。用户线程提交任务的execute
方法具体代码如下:
public void execute(Runnable command) {
//(1) 如果任务为null,则抛出NPE异常
if (command == null)
throw new NullPointerException();
//(2)获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
//(3)当前线程池线程个数是否小于corePoolSize,小于则开启新线程运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//(4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//(4.1)二次检查
int recheck = ctl.get();
//(4.2)如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//(4.3)否者如果当前线程池线程空,则添加一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//(5)如果队列满了,则新增线程,新增失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
1.代码(3)判断如果当前线程池线程个数小于 corePoolSize,如上图会在 workers 里面新增一个核心线程(core 线程)执行该任务。
2.如果当前线程池线程个数大于等于 corePoolSize 执行代码(4),如果当前线程池处于 RUNNING 状态则添加当前任务到任务队列,这里需要判断线程池状态是因为有可能线程池已经处于非 RUNNING 状态,而非 RUNNING 状态下是抛弃新任务的(比如执行了shutdown方法【advanceRunState(SHUTDOWN);置为shutdown状态】以及shutdownNow方法【advanceRunState(STOP);置为stop状态】)。当线程池中的核心线程都处于空闲状态时(没有任何任务去执行,这些线程都处于等待队列当中,调用了getTask方法中的workQueue.take()来阻塞线程),再去添加一个任务的时候,都会通过workQueue.offer(commond)加入到阻塞队列并唤醒核心线程(从等待队列Condition移到同步队列中)去执行新增的任务,因为如果核心线程没有设置超时销毁的话。
3.如果任务添加任务队列成功,则代码(4.2)对线程池状态进行二次校验,这是因为添加任务到任务队列后,执行代码(4.2)前有可能线程池的状态已经变化了,这里进行二次校验,如果当前线程池状态不是 RUNNING 了则把任务从任务队列移除,移除后执行拒绝策略;如果二次校验通过。
则执行代码(4.3)重新判断当前线程池里面是否还有线程,如果没有则新增一个线程。这个地方为什么会出现判断是否还有线程呢,按道理说线程池里面一直会存在核心线程的,不设置allowCoreThreadTimeOut的值为true的话(核心线程的超时时间),核心线程会一直存在线程池当中,则方法workerCountOf()不会等于0,那么出现线程池中没有线程的场景如下:该线程池设置了keepaliveTime参数(线程空闲时间)和allowCoreThreadTimeOut参数(开启核心线程空闲时超时销毁),当线程A执行到(4.1)处的代码,此时A的任务已经加入到了队列当中。线程B执行到了runWorker方法,进入getTask方法获取队列中任务中,
(1). 因为allowCoreThreadTimeOut为true,所以timed定为true。
(2).timedOut初始值为false,所以第一遍循环不会执行里面的内容。
(3).执行workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)检索并删除此队列的头,等待指定的时间。当队列中一直不为空的话,则一直会返回一个任务去执行,当且仅当任务队列为空,返回的Runnable为null,则timedOut设置为true,再次循环执行到第二步,满足了条件(allowCoreThreadTimeOut==true 且队列workQueue为空)则执行compareAndDerementWorkerCount(c)原子操作对核心线程的数量减一。
如果此时线程B执行了这段代码导致核心线程的数量为0,那么线程A获取到的线程数量就是0,则进入(4.3)addWorker(null,false),
为什么添加一个null的任务呢,因为线程A进入了(4.1)则说明已经添加到了队列中,在后面的runWorker方法中的while循环条件,如果task为null,则从getTask方法中的任务队列去获取任务来执行。
4.如果代码(4)添加任务失败,则说明任务队列满了,则执行代码(5)尝试新开启线程(如上图 thread 3 和 thread 4)来执行该任务,如果当前线程池线程个数 > maximumPoolSize
则执行拒绝策略。
接下来看新增线程的 addWorkder
方法的源码,如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//(6) 检查队列是否只在必要时为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//(7)循环cas增加线程个数
for (;;) {
int wc = workerCountOf(c);
//(7.1)如果线程个数超限则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//(7.2)cas增加线程个数,同时只有一个线程成功
if (compareAndIncrementWorkerCount(c))
break retry;
//(7.3)cas失败了,则看线程池状态是否变化了,变化则跳到外层循环重试重新获取线程池状态,否者内层循环重新cas。
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
//(8)到这里说明cas成功了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//(8.1)创建worker
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//(8.2)加独占锁,为了workers同步,因为可能多个线程调用了线程池的execute方法。
mainLock.lock();
try {
//(8.3)重新检查线程池状态,为了避免在获取锁前调用了shutdown接口
int c = ctl.get();
int rs = runStateOf(c);
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//(8.4)添加任务
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//(8.5)添加成功则启动任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
如上代码主要分两部分,第一部分的双重循环目的是通过 cas 操作增加线程池线程数,并且将入参的任务包装成worker对象,如下图Worker内部类,实现了Runnable以及继承AQS来实现并发操作,执行任务的线程就是这个worker创建的线程,入参的任务虽然也是实现Runnable接口,但是最终调用的是run方法,而并没有再次创建线程去执行,从而实现真正意义上的线程池复用。
第二部分主要是并发安全的把任务添加到 workers 里面,并且启动任务执行。
先看第一部分的代码(6),如下所示:
rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty())
相当于:
s >= SHUTDOWN &&
(rs != SHUTDOWN ||//(1)
firstTask != null ||//(2)
workQueue.isEmpty())//(3)
如上代码,也就是说代码(6)在下面几种情况下会返回 false:
1.当前线程池状态为 STOP,TIDYING,TERMINATED;
2.当前线程池状态为 SHUTDOWN 并且已经有了第一个任务;
3.当前线程池状态为 SHUTDOWN 并且任务队列为空。
回到上面看新增线程的 addWorkder
方法,发现内层循环作用是使用 cas 增加线程,代码(7.1)如果线程个数超限则返回 false,否者执行代码(7.2)执行 CAS 操作设置线程个数,cas 成功则退出双循环,CAS 失败则执行代码(7.3)看当前线程池的状态是否变化了,如果变了,则重新进入外层循环重新获取线程池状态,否者进入内层循环继续进行 cas 尝试。
执行到第二部分的代码(8)说明使用 CAS 成功的增加了线程个数,但是现在任务还没开始执行,这里使用全局的独占锁来控制把新增的 Worker 添加到工作集 workers。代码(8.1)创建了一个工作线程 Worker。
代码(8.2)获取了独占锁,代码(8.3)重新检查线程池状态,这是为了避免在获取锁前其他线程调用了 shutdown 关闭了线程池,如果线程池已经被关闭,则释放锁,新增线程失败,否者执行代码(8.4)添加工作线程到线程工作集,然后释放锁,代码(8.5)如果判断如果工作线程新增成功,则启动worker的工作线程,执行worker对象中的run方法。
3.2 工作线程 Worker 的执行
当用户线程提交任务到线程池后,具体是使用 worker 来执行的,先看下 Worker 的构造函数:
Worker(Runnable firstTask) {
setState(-1); // 在调用runWorker前禁止中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//创建一个线程
}
如上代码构造函数内首先设置 Worker 的状态为 -1,是为了避免当前 worker 在调用 runWorker
方法前被中断(当其它线程调用了线程池的 shutdownNow 时候,如果 worker 状态 >= 0 则会中断该线程)。这里设置了线程的状态为 -1,所以该线程就不会被中断了。如下代码运行 runWorker 的代码(9)时候会调用 unlock 方法,该方法把 status 变为了 0,所以这时候调用 shutdownNow 会中断 worker 线程了,如图 调用shutDownNow方法后,执行中断的方法。
再看看Worker内部类的runWork方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); //(9)status设置为0,允许中断
boolean completedAbruptly = true;
try {
//(10)
while (task != null || (task = getTask()) != null) {
//(10.1)
w.lock();
...
try {
//(10.2)任务执行前干一些事情
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();//(10.3)执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//(10.4)任务执行完毕后干一些事情
afterExecute(task, thrown);
}
} finally {
task = null;
//(10.5)统计当前worker完成了多少个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//(11)执行清工作
processWorkerExit(w, completedAbruptly);
}
}
如上代码(10)如果当前 task==null 或者调用 getTask 从任务队列获取的任务返回 null,则跳转到代码(11)执行。如果 task 不为 null 则执行代码(10.1)获取工作线程内部持有的独占锁,然后执行扩展接口代码(10.2)在具体任务执行前做一些事情,代码(10.3)具体执行任务,代码(10.4)在任务执行完毕后做一些事情,代码(10.5)统计当前 worker 完成了多少个任务,并释放锁。
这里在执行具体任务期间加锁,是为了避免任务运行期间,其他线程调用了 shutdown 或者 shutdownNow 命令关闭了线程池。其中有两个可扩展的方法在threadPoolExecutor没有具体执行逻辑,beforeExecute()和afterExecute(),用户可以自己重写这两个方法,一般用于对线程池中的任务执行过程的实时监控,执行任务前的,执行完成后的。
下面看看获取任务队列getTask方法的源码:
private Runnable getTask() {
// 5.1 设置从非阻塞的poll方法设置超时时间来获取头队列还是从阻塞式的take方法获取队列
boolean timedOut = false; // Did the last poll() time out?
// 循环 执行
for (;;) {
//
int c = ctl.get();
// 获取状态值
int rs = runStateOf(c);
// 5.2 检查此时是否调用了关闭线程池
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 获取 线程数量
int wc = workerCountOf(c);
// 5.3 是否设置allowCoreThreadTimeOut 以及 线程池数量是否超过核心线程数
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 5.4 有几种情况 会执行自减 线程数
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 5.5 非阻塞和阻塞获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果获取到任务不为null,直接返回 退出方法
if (r != null)
return r;
// 返回null的任务,设置 timeOut为true。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
代码(5.2) 第一:如果 rs状态为 SHUTDOWN 并且 workQueue 的队列为空。
第二:rs 状态 为 STOP、TIDYING、TERMINATED 这三种状态值。
执行decrementWorkerCount() 方法 循环 自减 线程数。
代码 (5.4) 会有一下几种情况会执行 cas 自减 操作。如果创建的线程数大于核心线程,那么会销毁超过核心线程数的部分,只保留核心线程数,策略就是根据设置的keepAliveTime值(线程空闲的时间值),超过这个值就会被销毁掉。如果也设置了allowCoreThreadTimeOut的值,那么核心线程也会依照这个按时销毁的策略去将空闲超过这个值的线程给销毁掉。
第一:当前线程数 大于 最大线程数 并且 wc > 1 或者 任务队列 为空。
第二:timed 为true (为true 的条件:设置allowCoreThreadTimeOut为true:允许核心线程超时销毁或者创建的线程数量大于核心线程)并且 timedOut为true(为true的条件是任务队列中没有任何任务,返回null,则执行timedOut = true 的代码) 并且 线程数大于1 或者 任务队列为空。
代码 (5.5) 根据timed 来决定采用是非阻塞的(poll 检查并返回队列头任务,如果没有则返回null)还是阻塞的(take 方法获取任务,如果队列中没有任务则阻塞挂起直到能够获取到任务才返回task)获取任务队列的task,其中poll(timeout,TimeUnit unit)方法指定超时时间,当队列中没有任何任务的时候,就会等待timeOut时长,如果在这段时间还没有任务加入到任务队列中,那么就直接返回null。
执行清理的任务:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//(11.1)统计整个线程池完成的任务个数,并从工作集里面删除当前woker
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//(11.2)尝试设置线程池状态为TERMINATED,如果当前是shutdonw状态并且工作队列为空
//或者当前是stop状态当前线程池里面没有活动线程
tryTerminate();
//(11.3)如果当前线程个数小于核心个数,则增加
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);
}
}
如上代码(11.1)统计线程池完成任务个数,可知在统计前加了全局锁,把当前工作线程中完成的任务累加到全局计数器,然后从工作集中删除当前 worker。
代码(11.2)判断如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有活动线程则设置线程池状态为 TERMINATED,如果设置为了 TERMINATED 状态还需要调用条件变量 termination 的 signalAll()
方法激活所有因为调用线程池的 awaitTermination
方法而被阻塞的线程
代码(11.3)则判断当前线程里面线程个数是否小于核心线程个数,如果是则新增一个线程,如果设置了allowCoreThreadTimeOut的值,true返回0,false返回核心线程数。
shutdown 操作:调用 shutdown 后,线程池就不会在接受新的任务了,但是工作队列里面的任务还是要执行的,该方法立刻返回的,并不等待队列任务完成在返回。如果不调用shutdown方法,那么线程池会一直保持下去,以便随时执行被添加的新Task任务,代码如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//(12)权限检查
checkShutdownAccess();
//(13)设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
//(14)设置中断标志
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
//(15)尝试状态变为TERMINATED
tryTerminate();
}
如上代码(12)检查如果设置了安全管理器,则看当前调用 shutdown 命令的线程是否有关闭线程的权限,如果有权限则还要看调用线程是否有中断工作线程的权限,如果没有权限则抛出 SecurityException
或者 NullPointerException
异常。
其中代码(13)内容如下,如果当前状态 >= SHUTDOWN 则直接返回,否者设置当前状态为 SHUTDOWN:
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
代码(14)内容如下,设置所有空闲线程的中断标志,这里首先加了全局锁,同时只有一个线程可以调用 shutdown 设置中断标志,然后尝试获取 worker 自己的锁,获取成功则设置中断标识,由于正在执行的任务已经获取了锁,所以正在执行的任务没有被中断。这里中断的是阻塞到 getTask()
方法,企图从队列里面获取任务的线程,也就是空闲线程。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//如果工作线程没有被中断,并且没有正在运行则设置设置中断
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
代码(15)判断如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有活动线程则设置线程池状态为 TERMINATED,如果设置为了 TERMINATED 状态还需要调用条件变量 termination 的 signalAll()
方法激活所有因为调用线程池的 awaitTermination 方法而被阻塞的线程
shutdownNow 操作
作用是中断所有的任务task,并且抛出InterruptedException异常,前提是在Runnable中使用if(Thread.currentThread().isInterrupted() == true)语句来判断当前线程的中断状态,而未执行的线程则不再执行,也就是从执行队列中清除。如果没有使用if(Thread.currentThread().isInterrupted() == true)语句及抛出异常的代码,则池中正在运行的线程直到执行完毕,而未执行的线程不再执行,也从执行队列中清除。
调用 shutdownNow 后,线程池就不会在接受新的任务了,并且丢弃工作队列里面里面的任务,正在执行的任务会被中断,该方法是立刻返回的,并不等待激活的任务执行完成在返回。返回值为这时候队列里面被丢弃的任务列表。代码如下:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//(16)权限检查
advanceRunState(STOP);//(17) 设置线程池状态为stop
interruptWorkers();//(18)中断所有线程
tasks = drainQueue();//(19)移动队列任务到tasks
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
如上代码首先调用代码(16)检查权限,然后调用代码(17)设置当前线程池状态为 stop,然后执行代码(18)中断所有的工作线程,这里需要注意的是中断所有的线程,包含空闲线程和正在执行任务的线程,代码如下:
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
然后代码(19)移动当前任务队列里面任务到 tasks 列表。