定义
线程池的定义应该都耳熟能详了,主要是对线程资源的管理,及统计分析。避免线程频繁创建于销毁影响性能等。
ThreadPoolExecutor的继承关系
ThreadPoolExecutor extends AbstractExecutorService
AbstractExecutorService implements ExecutorService
ExecutorService implements Executor
- Executor是顶层的抽象, 里面定义了一个execute(Runnable command)方法。 描述提供一个任务进行执行。
- ExecutorService对Executor进行了拓展,主要增加了生命周期(shutdown,shutdownNow等)、submit(Runnable r)任务提交等方法。
- AbstractExecutorService对ExecutorService做了基本的实现,主要是针对submit()任务提交的统一抽象封装,以及invokeAny()实现。
- ThreadPoolExecutor线程池的具体实现。
ThreadPoolExecutor创建参数
ThreadPoolExecutor提供了几个重载的构造方法, 我们看看参数最全的一个构造方法,其它构造方法都是通过this(...)不断调用到这里。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
构造方法内部只是一些安全检查及简单的属性赋值。这里主要是理解每个参数的含义:
- corePoolSize 核心线程池数量。刚开始提交任务线程池中的线程数量没有达到corePoolSize,此时会创建新的核心线程并将任务作为此线程的第一个任务。(默认不会被回收,除非设置了allowCoreThreadTimeOut,此时核心线程空闲时间到达keepAliveTime会被回收)
- maximumPoolSize 最大线程数量。当线程数量达到corePoolSize数量并且任务队列也满了, 线程的数量未到达maximumPoolSize。 会创建线程来执行(这里有个细节,其实在任务添加到队列之后,会二次检查线程数量,来决定是否增加线程)。 当非核心线程空闲时间到达keepAliveTime会进行回收。
- keepAliveTime 线程空闲的存活时间
- unit 指定keepAliveTime时间的单位, 最终通过util计算出一个纳秒级的keepAliveTime时间
- workQueue 任务队列,当线程数量到达corePoolSize后会将任务添加到队列中。
- threadFactory 线程创建的工厂,实际开发中主要用来自定义一些有意义的线程名称。
- handler 拒绝处理器。当任务队列已经满了,线程数量也到达maximumPoolSize,此时会调用handle进行处理。(这里其实还有一些时机如线程池已经不是running状态也会使用这里的拒绝策略处理)
ThreadPoolExecutor的状态
并发库是Doug Lea前辈写的,ThreadPoolExecutor的状态自然不是像我们日常开发一样动不动来几个int变量。我们先看看关于状态的源码:
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对象,用其高3位来描述线程池的运行状态,低29位来描述线程池中线程的数量。
接下来我们具体分析一下状态。
线程池状态
- RUNNING 运行中状态,可以提交/获取新的任务并执行
private static final int RUNNING = -1 << COUNT_BITS;
RUNNING的值是将-1(11111111111111111111111111111111)左移COUNT_BITS(29位),最终得到的值高三位等于1,低29位等于0:11100000000000000000000000000000(十进制-536870912)
- SHUTDOWN 不能提交新的任务,但是已有的任务会继续执行。
private static final int SHUTDOWN = 0 << COUNT_BITS;
SHUTDOWN的值将0左移COUNT_BITS(29位),最终还是0
- STOP 不能提交新的任务,中断执行中的任务,并将队列中等待的任务移除。
private static final int STOP = 1 << COUNT_BITS;
STOP的值是将1左移COUNT_BITS(29位),最终得到的值最高位等于1,低29位等于0:100000000000000000000000000000(十进制536870912)
- TIDYING 所有任务都执行完成且工作线程为0将。
private static final int TIDYING = 2 << COUNT_BITS;
TIDYING的值是将2左移COUNT_BITS(29位),最终得到的值最高位等于1,低30位等于0:1000000000000000000000000000000(十进制1073741824)
- TERMINATED 终止
private static final int TERMINATED = 3 << COUNT_BITS;
TERMINATED的值是将3左移COUNT_BITS(29位),最终得到的值最高2位等于1,低29位等于0:1100000000000000000000000000000(十进制1610612736)
通过ctl计算线程池运行状态
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static int runStateOf(int c) { return c & ~CAPACITY; }
- 首先计算CAPACITY,等于1左移29位之后减一,等于00011111111111111111111111111111(也就是线程数量的最大值:536870911)
- 计算运行状态runState等于c & ~CAPACITY。通过上一步知道CAPACITY的二进制是高三位为0低29位为1,然后取反意味着高3位等于1低29位等于0(11100000000000000000000000000000)。最后跟当前ctl的值进行&运算,其实就是计算高三位(忽略了低29位)的状态结果。
线程池运行状态小结
- 只通过ctl的高3位来描述状态值
- 各个状态值的大小关系是:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
- 在计算当前运行状态时,很巧妙的根据CAPACITY取反,然后跟高3位进行&运算得到状态结果值。
线程数量
理解了上面线程池运行状态的计算过程,线程数量就简单了。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static int workerCountOf(int c) { return c & CAPACITY; }
- CAPACITY线程容量的计算是一样的,最大值就是00011111111111111111111111111111(536870911)
- 利用当前ctl的值跟CAPACITY进行&运算(忽略了最高3位)得到低29的的计算结果。
任务提交流程
Worker
Worker是线程池里面的对象,也就是执行我们提交任务的工作线程。下面看看Worker的源码被定义在ThreadPoolExecutor内部。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/** worker中具体的线程对象 */
final Thread thread;
/** 初始化任务,首次提交的任务*/
Runnable firstTask;
/** 当前worker完成的任务数量 */
volatile long completedTasks;
/**
构造方法,基于firstTask。并通过线程工厂创建一个新的线程。
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
// 调用外部ThreadPoolExecutor的runWorker方法执行任务
runWorker(this);
}
// 下面是Lock methods的实现
// state等于-1代表还未开始. interruptIfStarted()会有相应判断
// state等于0代表已解锁.
// state等于1代表已锁定.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
// 只有大于等于0的状态才会去执行中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
小结一下:
- Worker包装了一个内部线程来执行任务,在构造方法将传入的firstTask作为其首次执行的任务。 内部线程会通过线程工厂来创建(就是我们创建线程池传入的工厂对象)。
- Worker继承了AQS,本身也实现了相关方法来实现不可重入的互斥锁。以此来确保Worker执行任务过程中不会被中断。
- 为了防止内部线程在真正运行之前被中断,构造方法会调用AQS父类的setState(-1)。
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);
}
前两行空值判断,下面逐步拆解详细分析一下执行流程。
// 这里第一次获取ctl的值。
int c = ctl.get();
/*
外层if中workerCountOf(c)计算当前线程的数量是否小于corePoolSize。
*/
if (workerCountOf(c) < corePoolSize) {
// 添加核心线程,并将任务作为其首次执行的任务
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果不符合条件执行后续流程
这里需要注意一个细节:
因为execute()方法本身是没有做任何同步处理的,虽然这里外层if获取了当时的线程数量并进行比较, 但多线程并发时可能刚比对完其它线程也执行了这里的逻辑一起并发执行addWorker(),这就有可能超出corePoolSize。
所以addWorker()内部会以原子性的方式检查线程池运行状态以及线程数量。当不符合条件时返回值为false,就会进行后续的流程。
接下来我们分析后续的流程
// 1. 判断是否RUNNING状态,且任务添加队列成功
if (isRunning(c) && workQueue.offer(command)) {
// 1.1 再次获取ctl值
int recheck = ctl.get();
// 1.2 再次判断状态,如果不是RUNNING,则移除任务。并拒绝任务处理
if (! isRunning(recheck) && remove(command))
reject(command);
// 1.3 检查线程数量等于0会添加线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 2. 再次添加非核心线程执行任务,添加失败则拒绝任务
else if (!addWorker(command, false))
reject(command);
大致概括一下,外层if会先判断状态,并且将任务添加到队列。如果if条件未能满足则尝试添加新的线程去处理。
这里有一些细节:
- 在注释1.1会再次获取ctl值,然后注释1.2再次检查线程池状态是否正常,否则将移除任务并执行拒绝策略。
- 而注释1.2中的条件结果可能会不满足,这是执行1.3是确保在上次检查后已有的线程死了。所以为了确保任务能得到执行,再次添加了一个线程(这里可以看到第一个参数传入null,是因为任务已经被添加到队列中了)
- 注释2中,也就是最后的else if如果执行到这里意味着队列已满,会添加新的线程来出来,如果添加失败则执行拒绝策略。(还有一种可能会执行到这里,线程池状态不是RUNNING。此时addWorker就会失败,也会执行拒绝策略, 这也确保了当我们调用了如shutdown(), shutdownNow等方法新的任务会被直接拒绝)
addWork()添加工作线程
先贴源码.
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 1. 检查线程池状态是否正常,队列是否为空(将没必要再创建线程)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 2. 死循环检查操作线程数量(CAS)
for (;;) {
int wc = workerCountOf(c);
// 2.1 根据传入的标记校验线程数量是否超出了核心线程数或最大线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 2.2 尝试以CAS方式增加线程数+1
if (compareAndIncrementWorkerCount(c))
break retry; // 成功则退出循环执行后续逻辑
c = ctl.get(); // Re-read ctl
// 线程池状态发送变化,回到外层循环重新处理
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop 没有变化重新执行内层循环改变workCount
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 3. 创建一个新的work
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 3.1 通过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());
// 3.1.1 确保线程状态正常
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 3.1.1.1 将work添加到workers中等
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;
}
addWork()中主要做了两件事:
- 检查线程池状态,以及检查并递增线程数量
- 创建worker,然后通过加锁将worker添加到workers集合中后解锁,最终启动线程等。
这里有一些细节需要注意:
- new Worker()将firstTask传入作为其首次执行的任务。 另外在Worker继承AQS,在构造方法中,内部会通过threadFactory来创建一个线程。
- 当判断worker中的线程创建失败(可能线程创建工厂创建线程异常或oom),或未能成功启动线程,最终会调用addWorkerFailed()进行回滚操作。
addWorkerFailed()源码分析
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 已经创建了worker,对其删除
if (w != null)
workers.remove(w);
// 循环cas方式对线程数量减一
decrementWorkerCount();
// 尝试终止线程池(内部会有条件判断,不符合直接return)
tryTerminate();
} finally {
mainLock.unlock();
}
}
任务执行流程
runWorker() 执行任务
Worker内部的线程启动后最终会回调Worker的run()方法,而run()方法主要委托ThreadPoolExecutor.runWorker()来工作。下面是源码:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 1. 将创建Worker的firstTask赋值
Runnable task = w.firstTask;
w.firstTask = null;
// 2. 解锁
w.unlock(); // allow interrupts
// 3. 记录是否突然完成,供后续流程分支处理
boolean completedAbruptly = true;
try {
// 4. 判断task不为null,循环处理
while (task != null || (task = getTask()) != null) {
// 4.1 执行前加锁
w.lock();
// 4.2 判断当前工作线程是否中断。如果线程池运行状态>=STOP确保线程中断,不是将不会中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 4.3 任务执行前处理
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 4.4 执行任务
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 {
// 4.5 任务执行后处理
afterExecute(task, thrown);
}
} finally {
// 4.6 worker记录完成线程数量并释放锁
task = null;
w.completedTasks++;
w.unlock();
}
}
// 5. worker正常完成修改标记
completedAbruptly = false;
} finally {
// 6. 处理worker退出逻辑
processWorkerExit(w, completedAbruptly);
}
}
这里我们再详细叙述一下执行流程:
- 注释1将task默认为firstTask,如果firstTask!=null则意味着首次执行的一定是firstTask。注释4中先判断了task!=null为true即可执行,当没有首次任务或已经执行过将通过getTask()获取。
- 注释2调用w.unlock()可能会让人误解。 这其实跟Worker对AQS的实现以及线程池关闭等因素有关联。 默认创建的worker状态是-1确保不会被中断,所以这里调用unlock()相当于将锁释放状态改为0.
- 注释4中循环获取任务,直到返回null或抛出异常才会结束。
- 注释4.1会在任务获取后执行前先加锁,主要是确保正在执行中的任务不会被中断。
- 注释4.3是在任务执行前提供回调处理,注释4.4是真正调用任务执行、注释4.5是在任务执行之后回调处理(典型的模板方法模式)
- 注释6执行worker退出逻辑。
小结一下:
- 通过分析知道,如果线程池状态是<=SHUTDOWN时会正常执行任务不会被中断。
- 任务执行中会有一些回调方法供我们拓展,但是如果抛出异常(或者任务本身执行异常)可以在afterExecutor()方法中获取,但是最终还会往外抛出导致worker退出。
processWorkerExit() 退出清理
show code...
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 1. 提前退出减少工作线程数量
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
// 2. 加锁去除workers集合中的对象, 并记录总的任务数量
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 3. 试图终止线程池
tryTerminate();
int c = ctl.get();
// 4. 判断是否为RUNNING,SHUTDOWN状态
if (runStateLessThan(c, STOP)) {
// 4.1 worker正常完成
if (!completedAbruptly) {
// 4.1.1 根据允许核心线程超时回收,计算min
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 4.1.2 计算min,如果队列不为空至少保证补充一个线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 4.1.3 线程数量超出min将不添加线程直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 4.2 添加非核心线程
addWorker(null, false);
}
}
小结一下:
- 注释1到注释3都是在做清理工作,将worker移除,记录完成任务数等。
- 注释4.1中的逻辑根据一系列条件判断是否补充一个线程到线程池中。
- 注释4.2 addWorker()新增一个非核心线程。 能执行到这里可能的情况有:1. worker异常退出,2. 当前线程数量低于核心线程数量,3. 队列不为空且没有工作线程。
任务的拒绝策略
通过前面的分析知道,当任务队列已满且线程数量到达maxPoolSize,此时将调用拒绝策略对任务进行处理。 ThreadPoolExecutor提供了几种默认的处理方式:
- CallerRunsPolicy 提交任务的线程来处理
- AbortPolicy 直接抛出异常
- DiscardPolicy 直接丢弃
- DiscardOldestPolicy 丢弃任务队列中最老的
实际开发中上面的策略可能不满足,一般都会记录下来并进行预警。能触发拒绝策略意味着线程池配置需要调整,或系统资源不够负载过大等。
ThreadPoolExecutor销毁
线程池关闭主要有shutdown()以及shutdownNow()两个方法。下面详细分析一下。
shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 1. 安全校验等
checkShutdownAccess();
// 2. 将线程池运行状态改为SHUTDOWN
advanceRunState(SHUTDOWN);
// 3. 中断空闲的Worker
interruptIdleWorkers();
// 4. 回调,供自定义实现
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终止
tryTerminate();
}
- 注释2中会循环通过CAS的方式更改状态
- 注释3中会执行具体的中断动作,主要是将空闲的worker进行中断。 下面是源码
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历每个worker
for (Worker w : workers) {
Thread t = w.thread;
// 没有被标记中断,且能获取到所才执行中断
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 只中断一个worker
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
上面的逻辑还是很清晰的,主要是看worker的线程是否已经中断,如果没有则尝试获取lock(这里也是确保正在执行的任务不会被中断)成功后对线程进行中断。
shutdownNow()
shutdownNow()相比shutdown()会粗暴一点, 下面是源码。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
// 中断所有worker
interruptWorkers();
// 将在队列等待执行的任务移除并返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试中断线程池
tryTerminate();
return tasks;
}
相比shutdown(), 这里有两个不同点:
- 会中断所有的worker, 只要worker的状态不是-1,则调用其中断方法。
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) {
}
}
}
- 将在队列等待执行的任务移除并返回
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
// 1. 将等待执行的任务移除到taskList中
q.drainTo(taskList);
// 2. 判断队列不为空,遍历进行remove并添加到taskList
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
这里注释2可能会有点不清楚, 当queue是DelayedWorkQueue等,在任务没有过期前drainTo方法是不会被移除的。 需要调用remove方法一个一个来操作。这里想知道细节的可以参考:java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue#drainTo(java.util.Collection<? super java.lang.Runnable>)
shutdown() & shutdownNow()
这里总结一下两者的差别:
- shutdown()将状态改为SHUTDOWN,并只会空闲worker进行中断。
- shutdownNow()将状态改为STOP,并将所有worker进行中断,移除队列中等待执行的任务列表并返回。
游离在各个角落的tryTerminate()
先看看源码
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 判断各个状态及队列情况是否能执行执行后续操作。
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 线程数量还大于0,会中断当前线程。
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 尝试将状态改为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 成功更改状态则调用terminated() 默认空实现
terminated();
} finally {
// 将状态改为TERMINATED ctl.set(ctlOf(TERMINATED, 0));
// 唤醒等待终止条件的线程,主要是调用awaitTermination()的线程 termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// 再次循环执行
}
}
小结一下:
- 能转变到TERMINATED状态的条件是:1). SHUTDOWN状态且线程数量为0,队列任务数量为空。2). STOP状态且线程数量为0。
- 成功变为TERMINATED状态后会唤醒其它调用awaitTermination()的线程。
总结
-
对线程池的一生进行了分析。平时大部分人可能只是根据规范创建线程池提交任务,但了解内部细节会更好的应对各种异常情况。
-
从线程池的设计可以看到前辈们编程的功力多么深厚, 膜拜,好好学习。