ThreadPoolExecutor
在我们日常操作中经常使用,常见的那些使用基本使用方法和就不说了,这篇分析一下基本源码(其实是因为源码比较少)
首先看一下定义的基本参数
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;
// runState is stored in the high-order bits
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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl :
这个是记录的当前的状态信息,是一个int
类型,总计32位
这。里分为两块内容,高3位
记录的是当前的线程池状态,也就是下面的RUNNING
,SHUTDOWN
等,低29位
记录的是当前运行的线程的个数。这个构建方式和我们常见的MeasureSpec
类似。这里通过AtomicInteger(ctlOf(RUNNING, 0))
设置了当前的默认状态RUNNING
,以及线程个数0
COUNT_BITS :
位运算的偏移个数标记,Integer.SIZE
是32
,所以这里是29
CAPACITY :
这个是容量的标记mask属性,就是1进行左移29位后减一,根据位运算的结果就是0001 1111 1111 1111 1111 1111 1111 1111
总计29
个1,方便后面进行&
运算获取运行的线程个数
RUNNING :
正常运行状态,也是最初的状态
SHUTDOWN :
关闭状态,通过调用shutdown
方法会设置此状态,shutdown
方法并不会打断正在执行的线程
STOP :
停止状态,通过调用shutdownNow
方法会设置此状态,shutdownNow
会去打断正在执行的线程
TIDYING :
每当一个线程任务执行完毕后会调用tryTerminate
这个方法,如果当前线程已经调用了shutdown
等方法修改了状态,那么后面会再进行一系列判读,如果都满足就会使用CAS
把当前的线程池状态修改成TIDYING
,然后执行完terminated
方法后会立刻再修改状态为最终的TERMINATED
状态
TERMINATED :
终止状态,也是一个线程池的最后一个状态, 表面这个线程已经完全停止,里面的任务个数是0,并且也不再接受新的任务,其实SHUTDOWN
及后面的状态全都不再接受新的任务插入了
这几个状态是依次递增的
,这个要记住,后面判断状态会用到这个特点
知道这些参数的意义,那么上面几个方法意义也就明确了
runStateOf :
获取传入参数的高3位
的值,其实就是上面的几个状态
workerCountOf :
获取传入参数低29位
的值,这里也就是活动的任务个数
ctlOf:
这个可以类比于MeasureSpec
的makeMeasureSpec
,根据两个值|
运算计算新的值,这里一般是上面的状态(只有高3位有值)
和任务个数(只有低29位有值)
组成新的值
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final Condition termination = mainLock.newCondition();
private final HashSet<Worker> workers = new HashSet<>();
private int largestPoolSize;
private long completedTaskCount;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
workQueue :
阻塞队列,当核心线程满了后,后面添加的任务会
mainLock :
可重入锁,用来做同步处理的
termination :
ReentrantLock
的条件处理策略Condition
,类似有wait
和notify
;可以通过await
和signal
阻塞和唤醒指定的线程
workers :
当前执行的任务的集合,这个里面存储的Worker
本身继承了AQS
,同时实现了Runnable
,也就是Worker
本身既是任务执行者也可以当成锁来用,而且Worker
内部会有循环取任务的处理,如果阻塞队列里有任务,一个Worker
是可以执行多个任务的,并不一定执行一次就马上退出
largestPoolSize :
线程池中最大线程个数,这个记录的是线程中曾经出现过的最大的线程个数
completedTaskCount :
完成的任务个数,每个Worker
都可能会完成多个任务,当Worker
最终执行完关闭后会把其完成的任务个数进行累加,completedTaskCount
是所有已完成的任务的总和
threadFactory :
线程工厂,就是执行任务的线程的生产工厂,所有的线程都是从这里产生的,最简单的我们可以通过new Thread
去生产一个Thread对象
handler :
拒绝策略,当所有的核心线程满了,队列满了,最大线程也满了时,再往线程池里放任务就会走这个策略,默认的是defaultHandler = new AbortPolicy()
,即抛出异常
keepAliveTime :
保持存活的时长,当一个Worker
执行完任务后最大等待时长,超过则关闭这个Worker
,其实这个就是通过阻塞队列的poll(long timeout, TimeUnit unit)
的阻塞超时时长进行控制的
allowCoreThreadTimeOut :
设置核心线程是否也使用超时,也就是上面的keepAliveTime
,一般核心线程是不会关闭的,使用take
方法阻塞获取任务,一直到有任务为止,设置这个后则和非核心线程一样通过poll(timeout,timeunit)
方法一样超过时间则关闭
corePoolSize :
核心线程数,这个就很基础了,一个线程池创建优先都是核心线程池先创建,然后阻塞队列,然后才是非核心线程,这个应该都知道
maximumPoolSize :
最大线程个数,就是包括核心线程在内的最大个数了,核心线程之外的就是非核心线程了,一般核心线程不会被关闭,非核心线程任务执行完后超过一定时间就回收了
线程池的入口是execute
方法,从这里入手即可
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
final void reject(Runnable command) {
//这个handler默认不设置就是使用defaultHandler进行初始化
handler.rejectedExecution(command, this);
}
代码很好理解,当线程个数小于corePoolSize
核心线程个数时,通过addWorker
添加一个任务,addWoker下面会讲,这里的第二个参数指的是是否是核心线程。
如果添加成功则直接返回,如果添加失败说明有可能其他线程已经添加完毕,当前核心线程已经满了。这时会获取到当前的状态值,如果还是正常的RUNNING
状态,那么就往workQueue
中offer
一个任务,RUNNING
状态一般不会变动,除非调用shutdown
等方法关闭线程池才会变动。
如果往阻塞队列workQueue
中添加成功,那么再检查一次状态。
如果这时被调用关闭方法了,状态变成非RUNNING
了,那么就移除该任务,同时执行拒绝策略。
否则如果当前的线程个数为0,这个就比较极端了,核心线程和非核心线程全执行完了,那么就添加一个非核心线程去执行任务,这里传入的第一个为参数null
,那么添加的时候就会从阻塞队列中去取任务,因为是非核心线程,所以超时取不到任务就会自动退出。
如果核心线程满了,阻塞队列也满了,添加失败了,那么就直接通过addWorker
方法添加到非核心线程中,如果添加失败则执行拒绝策略。
拒绝策略reject
执行比较简单就不说了,中心就到了addWorker
方法中,addWorker
方法稍微有点长
,我分两段讲,好在这两段没什么关联
第一段
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null
&&! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
...... //省略下一段
}
这里会发现,一上来就来了个死循环,那这里做了啥呢?
首先获取当前的运行状态,这里有一个判断语句,可以等价为这样的
if (rs >= SHUTDOWN &&(rs != SHUTDOWN || firstTask != null
|| workQueue.isEmpty()))
return false;
那么我们就可以得出结论
SHUTDOWN
之后的状态STOP
,TERMINATED
等直接添加失败返回false
SHUTDOWN
状态下,如果想添加一个新的任务直接失败。或者不添加任务想创建一个线程从队列中取任务,如果此时队列为空,也直接失败。
这个条件就涵盖大部分关闭线程的场景了,可以粗略认为SHUTDOWN
及之后的状态全部无法添加任务
之后内部又有一个死循环,先获取当前执行的线程数,其实就是Worker
的个数,一个线程对应一个Worker
,这里有两个判断,第一个是超过CAPACITY
的个数,这个我们一般不会超出暂不考虑。第二是根据core
进行线程数判断是否超过核心线程或者非核心线程,超出直接失败,这个对应于上面的添加核心线程失败的原因。
如果核心或非核心线程数未满,那么就通过CAS
把当前的线程数+1
,直接跳出循环
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
如果CAS
修改失败,那么就说明当前线程个数有变动,这个一般是有线程任务结束关闭退出了,这里会使用CAS
把当前线程数-1
,这时候就重新执行一遍内循环,重新检查一遍线程数,再重新CAS
增加线程个数。
这后面还有一个判断,如果当前状态发生变化,那么就可能变更为不可用状态了,这时直接跳到开头的retry
位置重来一次。
上面这么多代码实际就是给当前的ctl的线程个数的值+1
,没有别的。
再看第二段代码
private boolean addWorker(Runnable firstTask, boolean core) {
...... //省略第一段
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这里就有点意思了,如果上面的第一段满足,说明可以正常添加新的线程了。
这里一上来就新建了一个Worker
对象,先看一下Worker
的构造方法
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable{
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
}
public ThreadFactory getThreadFactory() {
return threadFactory;
}
上面提到Worker
本身就是一个Runnable
,在构造方法里会通过上面的threadFactory
创建一个新的线程,然后把线程的Runnable
指向Worker
本身,也就是这个线程start
方法调用后,会直接调用Worker
的run
方法。
线程工厂未指定会使用默认的DefaultThreadFactory
,就是普通的new
出新的线程出来
private static class DefaultThreadFactory implements ThreadFactory {
......
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
再回到上面的第二段代码,创建完Worker
后下面的代码会使用同步加锁。
获取当前的状态,如果是RUNNING
状态,或者是SHUTDOWN
的状态同时firstTask
为空,由上面第一段的代码我们指定,这时候的workQueue
是不为空的,这两种情况都满足添加Worker
的条件,因为可以直接添加或者从阻塞队列中取。如果当前线程已经启动了,那么就直接抛出异常,上面刚创建就被启动,这说明并发已经有先在执行了,这里直接放弃。一切正常,那么就添加到workers
列表中,然后记录最大的线程个数largestPoolSize
,同时把workerAdded
改为true
。
后面的就比较简单了,添加成功后直接启动Worker
的线程,其实就是使用Worker
内新建的线程执行Worker
的run
方法,启动成功就标记线程为已启动,后面未启动成功就进入addWorkerFailed
方法,addWorker
方法最终返回启动成功的状态。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
addWorkerFailed
方法只是很简单的移除任务,给当前的ctl
的线程个数自旋减1,这里有个tryTerminate
方法先留着后面讲。
下面看下Worker
的执行代码
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable{
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
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 {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
run
方法会走到内部的runWorker
方法中,这里会拿到构造方法传入的Runnable
任务,可能为空。如果这里拿到的任务不为空,或者用getTask
从阻塞队列中取出的任务不为空,才能进行下面的代码。这里是一个While
循环,也就是执行完一个后循环回来从队列中取,如果有再拿一个继续做。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
......
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
......
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask
中通过poll
方法进行非核心线程的任务获取,超过时间会返回null
,就直接跳出循环了,而核心线程默认是使用take
,会一直阻塞到有任务可以取为止。注意取任务这里如果线程池状态更改,则放弃任务返回空,并把活跃线程个数减1
如果当前线程池是STOP
及之后的状态,同时任务线程未被打断过,使用interrupt
打断当前线程。
如果当前线程是STOP
及之后的状态,如果线程被打断过,那么再打断一次。因为Thread.interrupted()
会重置interupt
状态值为false,后面那个一定是满足的,其实这个判断我不是很懂。
总之STOP
及以后的状态,如果要运行新的任务,直接打断就完事了,注意的是,这里的打断
只是说把当前线程的interupt
标记改成ture
,并不是说一定就把线程打死了,如果当前线程有阻塞状态wait
,sleep
等那么才会抛出异常,否则后续正常执行。
这里的打断前提是STOP
状态,而非SHUTDOWN
状态,也就是运行中的任务shutdown
方法并不会打断。
这里interupt
后仍然会执行task.run()
,就是要我们自己处理这个标记,beforeExecute
和afterExecute
都是空方法,可以自定义实现。这个run
中可能会有异常抛出,那么后面的completedAbruptly
就不会赋值为false
,同时会把异常个数统计数减1。
任务执行完后(打断也算),会把completedTasks++
然后执行processWorkerExit
方法,表示Work
正常退出。这里传入的completedAbruptly
一般是false
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
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);
}
}
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
这里会先移除执行完毕的Worker
,然后把Worker
内完成数completedTasks
累加到全局的计数上。
如果是RUNNING
或者SHUTDOWN
的状态,会进行下面的判断。为什么要允许SHUTDOWN
的状态,因为addWorker
方法中允许SHUTDOWN
状态时添加一个非核心线程的空任务Worker
去执行一定处理,这也就从侧面说明shutdown方法调用后,Worker虽然不支持添加新的任务,但仍然可以从原来的队列中取任务执行
1.
如果允许核心线程超时,当前队列不为空同时当前的线程个数大于1,或者队列为空,正常返回
2.
如果不允许核心线程超时,同时当前线程个数大于核心线程数,正常返回。
而最后的addWorker
什么时候会进入执行呢?其实就是completedAbruptly = true
或者有线程异常结束。这两种情况一般都是因为
1.
执行的任务Runnable
抛出异常导致completedAbruptly
并没有赋值为false
2.
核心线程异常关闭,对应于上面的第2条
,非核心不允许超时,但同时有线程关闭,导致核心线程小于corePoolSize
,那么就是核心线程的关闭
发生异常那么就通过addWorker
新建一个非核心线程去补齐线程数。
这里有个tryTerminate
也比较重要
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
如果是运行态RUNNING
,或者SHUTDOWN
态同时队列任务不为空,或者TIDYING
及之后的状态直接返回,TIDYING
是在这个方法的下面进行自旋设置,刚开始进来肯定是不满足这个的。
其实首次满足只有SHUTDOWN
态同时任务队列为空,或者STOP
态度,才会走到下面的代码。
如果活跃线程数大于0,那么通过interruptIdleWorkers
尝试打断空闲的线程。如果活跃线程已经变成0了,就是核心和非核心线程都关闭了,那么先自旋设置TIDYING
,最终设置成TERMINATED
,并唤醒所有termination
等待的线程。
有人可能会问,既然到这里线程池中线程已经全部关闭了,那为什么还要唤醒等待的线程?其实线程池中有termination
锁定等待的只有awaitTermination
方法,线程池内部是不会调用的,也就是给我们自己使用的。
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
while (!runStateAtLeast(ctl.get(), TERMINATED)) {
if (nanos <= 0L)
return false;
nanos = termination.awaitNanos(nanos);
}
return true;
} finally {
mainLock.unlock();
}
}
这个方法一般是用来检测线程池是否关闭了的,有超时时间设置,超过时间没被唤醒termination.awaitNanos
就会返回0或负数,也就是这个方法的返回值就是线程池是否已经关闭。通常配合shutdown
方法使用,比如:
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(testRunner); //耗时任务
executorService.shutdown();
System.out.println("任务开始执行 ");
boolean result = executorService.awaitTermination(1000, TimeUnit.MILLISECONDS);
System.out.println("当前线程池是否关闭 "+result);
这个结果可能是关闭也可能还未关闭,比较shutdown
并不会打断正在执行的线程。
看下面的shutdown
方法你就会明白为什么不会打断运行中的线程任务了
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
这里的checkShutdownAccess
和onShutdown
最终都是空方法,跳过。
advanceRunState
方法是自旋把当前线程池状态设置为SHUTDOWN
状态,重点看interruptIdleWorkers
方法。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
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();
}
}
可以看出如果当前Worker
的线程未被打断过,那么尝试获得Worker
这个锁,因为Worker
本身就继承了AQS
,所以可以当成一把锁来用的。tryLock
方法比较温和,尝试获得锁,获得不到就返回false,只有拿到这把锁才会执行interupt
,那么这把锁是在哪锁定的呢?
其实就在上面的runWorker
方法内,也就是任务正常执行期间是拿不到这个锁的,也就无法打断了,除非任务结束或抛出异常。
//省略部分代码
final void runWorker(Worker w) {
......
w.unlock(); // allow interrupts
try {
while (task != null || (task = getTask()) != null) {
w.lock();
try {
task.run();
} finally {
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
和shutdown
对应的就是shutdownNow
方法了
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
和shutdown
类似,会设置当前状态为STOP
,并调用interruptWorkers
方法,最后使用drainQueue
方法弹出所有阻塞队列中的任务,并全部从队列中移除,但是会把这些返给调用者,shutdownNow
方法是可以获得未执行的阻塞任务集合的。此外shutdown
和shutdownNow
最终都会调用上面提到的tryTerminate
方法。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
shutdown
就比较暴力了,不管三七二十一,只要当前任务线程不为空而且未被打断过,就直接打断。打断除了可打断正在执行的耗时Runnable
,对于处于获取任务状态的阻塞状态也是可以适用的,再回头看一下getTask
方法
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
打断的是最后面的workQueue.poll
或者workQueue.take()
方法,抛出异常后重新进入循环,那么此时是STOP
及以上状态,或者SHUTDOWN
状态,同时队列中没有任务了,就返回null
,那么最外面的runWorker
方法也就会终止退出了。所以说SHUTDOWN
比较温和,只有队列中没有剩余任务才会退出,并不是说本次正在执行的任务执行完就退出了,STOP
就很暴力,通篇都展示这STOP
的不讲道理。
1.
线程池通过添加Worker
的方式创建新的线程,其中Worker
中会使用ThreadFactory
构建新的线程,并保存传入的首次要执行任务,可能为空。可以说一个Worker
对应一个线程。
2.
Worker
中会运行初始化传入的Runnable
,如果为空,那么就从阻塞队列workQueue
中通过getTask
获取任务,如果当前没有任务,那么就阻塞。
3.
非核心线程的getTask
方法是有阻塞超时时间的,超出时间获取不到就最终返回null
,那么Worker
的执行任务的方法runWorker
也会终止,当前的Worker
结束,从线程池中移除。如果是核心线程,那么如果不设置允许核心线程超时,核心线程会一直阻塞到有任务可以取为止,不会终止。
4.
如果执行的Worker
任务抛出异常,同时当前线程池没有调用过shutdownNow
方法,也就是当前是STOP
前面的几种状态,那么会给线程池重新创建一个非核心线程进行补全。
5.
shutdown
方法并不打断正在执行任务的Worker
,因为Worker
执行任务前会把自身作为锁进行锁定,执行完一个任务(Runnable)后,会进行解锁。shutdown
方法会尝试获得Worker
本身这把锁,获取不到就不会打断了。那么对于一个正在运行的Worker
,当执行完当前的任务,循环再去取队列中的任务,发现没有新任务然后阻塞后,这时候是可以被shutdown
打断的。
6.
shutdown
方法执行后,线程池不再接受新的任务,原先队列中的任务保留。
7.
shutdownNow
方法执行后,线程池同样不再接受新的任务,原先队列中的任务会被全部弹出移除,同时这些任务会放在一个集合中,作为shutdownNow
的返回值。同时getTask
方法返回值全部为空(只要变成了STOP
状态就不给任务了)。
8.
shutdownNow
方法会强制执行所有Worker
的线程打断方法interupt
,而不像shutdown
那种先尝试获取锁。
9.
awaitTermination
可以配合shutdown
等方法,判断线程池是否已经完全关闭了,即没有线程在执行任务了。