阅读JDK线程池源码后总结的一些理解,包括:线程池的设计思想、状态转移、任务处理流程、拒绝策略原理、异常处理、状态监控等。
1 线程池的继承结构
首先看下 ThreadPoolExecutor
的继承结构,有助于理解线程池的设计思想。
1.1 Executor
Executor
是顶层接口,表示在将来的某个时间执行给定的任务。该接口提供了一种将任务提交与任务运行分离的机制,包括线程使用、调度等细节。
public interface Executor {
// 在将来的某个时间执行给定的命令
void execute(Runnable command);
}
该命令可以在新线程、池线程或调用线程中执行,具体由 Executor
的实现类决定:
同步执行器
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
异步执行器
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
按提交顺序可调度的执行器
class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<>();
final Executor executor;
Runnable active;
SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(Runnable r) {
tasks.add(() -> {
try {
r.run();
} finally {
scheduleNext();
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
1.2 ExecutorService
一种执行器(Executor),它提供管理终止任务的方法,以及可以生成 Future
用于跟踪一个或多个异步任务进度。ExecutorService
可以关闭,关闭后拒绝新任务,提供了两种不同的方法,shutdown
方法将允许在终止之前执行先前提交的任务,而 shutdownNow
方法将阻止等待任务启动并尝试停止当前正在执行的任务。
public interface ExecutorService extends Executor {
// 有序关闭,执行先前提交的任务,但不接受新任务
// 如果已经关闭,则调用没有额外的效果
// 此方法不等待以前提交的任务完成执行,可以使用使用awaitTermination达到这一效果
void shutdown();
// 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表
// 此方法不等待正在执行的任务终止,可以使用awaitTermination达到这一效果
// 该方法只是尽力停止处理正在执行的任务,不保证一定停止。例如,典型的实现将通过Thread.interrupt停止执行,若任务未能响应中断,则可能永远不会终止
List<Runnable> shutdownNow();
// 如果此执行器已关闭,则返回true
boolean isShutdown();
// 如果关闭后所有任务都已完成,则返回true
// 请注意,除非首先调用shutdown或shutdownNow,否则isTerminated永远不会为true
boolean isTerminated();
// 阻塞直到
// 1)调用shutdown后所有任务完成执行,2)发生超时,3)当前线程中断
// 执行该方法,以先发生者为准
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一个值返回任务以供执行,并返回一个Future,表示任务等待的结果
// Future的get方法将在成功完成后返回任务的结果
<T> Future<T> submit(Callable<T> task);
// 提交可运行任务以供执行,并返回表示该任务的Future
// Future的get方法将在成功完成后返回给定的结果 result
<T> Future<T> submit(Runnable task, T result);
// 提交可运行任务以供执行,并返回表示该任务的Future
// Future的get方法在成功完成后将返回null
Future<?> submit(Runnable task);
// 执行给定的任务,当所有任务完成时,返回保存其状态和结果的Future列表
// 对于返回列表的每个元素,Future.isDone都为true
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 和上面类似,也是执行所有任务,但是这里设置了超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 同上一个方法,只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果
// 超过指定的时间,抛出 TimeoutException 异常
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
1.3 AbstractExecutorService
提供 ExecutorService
执行方法的默认实现。此类使用 newTaskFor
方法实现 submit
、invokeAny
和 invokeAll
方法并返回 RunnableFuture
对象,默认为此包中提供的 FutureTask
类,子类可以重写 newTaskFor
方法以返回除了 FutureTask
之外的 RunnableFuture
实现。
1.4 ThreadPoolExecutor
一种 ExecutorService
实现,它使用可用池化线程执行每个提交的任务,通常使用 Executors
工厂方法进行配置。线程池解决了两个问题:执行大量异步任务时提供更好的性能,减少了每任务调用开销;它提供了一种方法,绑定和管理执行任务集合时消耗的资源(包括线程)。每个 ThreadPoolExecutor
还维护一些基本统计信息,例如已完成任务的数量。
2 处理的任务FutureTask
线程池在处理提交的 Runnable
和 callable
对象时,会将其包装成 FutureTask
对象,因此,理解 FutureTask
的继承结构对于理解线程池也是十分必要的。
2.1 Runnable
Runnable
接口的实现类,其实例被线程所执行,该类必须定义一个名为 run
的无参数方法。
public interface Runnable {
// 当使用实现接口Runnable的对象创建线程时,启动线程会导致在单独执行的线程中调用对象的run方法
public abstract void run();
}
2.2 Future
Future
表示异步计算的结果,其实现提供了检查计算是否完成、等待其完成以及检索计算结果的方法。只有当计算完成时,才能使用 get
方法检索结果,否则会阻塞直到计算完成。通过 cancel
方法取消执行。一旦计算完成,就不能再取消执行。如果希望为了可取消性而使用 Future
,但不需要执行的结果,则可以声明 Future<?>
形式的类型并作为基础任务的结果返回 null。
public interface Future<V> {
// 尝试取消执行此任务。
// 如果任务已完成、已取消或由于其他原因无法取消,则此尝试将失败
// 如果成功,并且在调用cancel时此任务尚未启动,则此任务不应运行
// 如果任务已启动,则mayInterruptIfRunning参数确定执行此任务的线程是否应中断以尝试停止任务
boolean cancel(boolean mayInterruptIfRunning);
// 如果此任务在正常完成之前被取消,则返回true
boolean isCancelled();
// 如果此任务已完成,则返回true
// 完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法都将返回true
boolean isDone();
// 等待计算完成,然后检索其结果
V get() throws InterruptedException, ExecutionException;
// 最多等待给定的时间以完成计算,然后检索其结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
2.3 RunnableFuture
可运行的 Future
,成功执行 run
方法将导致 Future
的变为完成状态,并允许访问其结果。
public interface RunnableFuture<V> extends Runnable, Future<V> {
// 将此 Future 设置为其计算结果,除非已取消
void run();
}
2.4 FutureTask
一种可取消的异步计算,此类提供 Future
的基本实现,包括启动和取消计算、检查计算是否完成以及查看计算结果的方法。只有在计算完成后才能检索结果;如果计算尚未完成,get
方法将阻塞。计算完成后,无法重新启动或取消计算。
FutureTask
可用于包装 Callable
或 Runnable
对象,因为 FutureTask
实现了 Runnable
,所以 FutureTask
可以提交给 Executor
执行。
FutureTask
中的 run
方法比较重要,线程池处理到一个 FutureTask
表示的任务后,会调用其 run
方法,在该方法中,会根据执行结果调用 setResult
、setException
,以实现 Future
特性:
3 AbstractExecutorService的方法实现
AbstractExecutorService
抽象类是对 ExecutorService
接口的基础实现,实现了几个实用的方法,这些方法提供给子类进行调用,这些方法包括 newTaskFor
、submit
、doInvokeAny
、invokeAny
、invokeAll
、cancelAll
。
这个抽象类实现了 invokeAny
方法和 invokeAll
方法,这里的两个 newTaskFor
方法也比较有用,用于将任务包装成 FutureTask
,定义于最上层接口 Executor
中的 void execute(Runnable command)
由于不需要获取结果,不会进行 FutureTask
的包装。
3.1 newTaskFor方法
将传入的 Runnable
、Callable
包装成 RunnableFuture
对象,其实现类为 FutureTask
;定义于最上层接口 Executor
中的 void execute(Runnable command)
由于不需要获取结果,不会进行 FutureTask
的包装。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
3.2 sumit方法
submit 方法的一般执行步骤是:
- 通过
newTaskFor
方法将Runnable
、Callable
包装成FutureTask
对象; - 通过
execute
方法将任务FutureTask
交给执行器执行;(AbstractExecutorService
并没有实现execute
方法,由子类实现) - 返回
FutureTask
对象引用;
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 将执行任务包装成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 提交任务给执行器执行
execute(ftask);
// 返回 Future
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
3.3 invokeAny、invokeAll 方法
这两个方法篇幅较长,且比较独立,对于理解线程池原理没有太大影响,这里就不展开了。
AbstractExecutorService
这个抽象类包装了一些基本的方法:submit
、invokeAny
、invokeAll
,它们都没有真正开启线程来执行任务,只是在方法内部调用了 execute
方法,所以最重要的 execute(Runnable runnable)
方法在这个抽象类中并没有实现,该方法的实现在 ThreadPoolExecutor
类中。
4 ThreadPoolExecutor实现类
ThreadPoolExecutor
是 JDK 提供的线程池实现,它实现了任务提交、线程管理、监控等线程池需要的方法。可以基于它来继续扩展,比如实现定时任务的类 ScheduledThreadPoolExecutor
就继承自 ThreadPoolExecutor
。
为了广泛的场景中适用,该类提供了许多可调整的参数和可扩展性钩子。当然也可以使用更方便的 Executors
工厂方法 Executors.newCachedThreadPool
(无限线程池,具有自动线程回收)、Executors.newFixedThreadPool
(固定大小线程池) 和 Executors.newSingleThreadExecutor
(单后台线程),这些方法为最常见的使用场景提供预先配置的线程池,以上工厂方法无法满足要求的话,可手动配置和调整此类。
我们通过一个图,直观看下线程池的执行逻辑:
- 调用
submit
将任务加入工作队列(ThreadPoolExecutor
实现中,当线程池中的线程数小于corePoolSize
时,直接生成新的线程直接执行,不需要放入工作队列); - 线程池中的线程不断从工作队列中获取待执行的任务进行执行;
4.1 内部类 Worker
ThreadPoolExecutor
将线程池中的线程包装成了一个 内部类 Worker
对象,就是线程池中做任务的线程。要有一个概念,任务是 Runnable
(内部叫 task 或 command),线程是 Worker
。
submit
方法中,参数是Runnable(Callable)
类型,这个参数不是用来启动线程的Runnable
对象,即:不是用来做这个事情的:new Thread(runnable).start();
这里
submit
的Runnable
对象是任务,任务的处理逻辑是Runnable
中的run()
/Callable
中的call()
方法里面定义的执行逻辑;我觉得完全可以定义一个新的
FunctionalInterface
叫做Task
,该接口定义一个run()
方法表示任务需要处理的逻辑,可能作者不想因为这个再定义一个完全可以用Runnable
来代替的接口;
Callable
的出现,是因为Runnable
没有返回值,不能满足需要返回结果的任务而提出的。
Worker
继承了抽象类 AbstractQueuedSynchronizer
,通过 AQS
写少量的代码就能实现自己需要的同步方式。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// 每个 worker 绑定一个 thread,用于执行任务
final Thread thread;
// 这里的 Runnable 是任务的意思,即task或command。
// 如果创建线程的时候指定了firstTask,线程启动时需要执行的第一个任务
// firstTask指定null的话,线程启动后,自己到任务队列中取任务
Runnable firstTask;
// 用于记录此线程完成的任务数,volatile保证可见性
volatile long completedTasks;
// Worker 构造方法,传入 firstTask,也可以传 null
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 调用 ThreadFactory 来创建一个新的线程
this.thread = getThreadFactory().newThread(this);
}
// 这里调用了外部类的 runWorker 方法
public void run() {
runWorker(this);
}
}
4.2 ThreadPoolExecutor接收的参数
不通过 Executors
,自己生成 ThreadPoolExecutor
实例的话,需要关注以下参数:
- int corePoolSize: 线程池中要保留的线程数,即使它们处于空闲状态,也不销毁,除非设置了 allowCoreThreadTimeOut=true;
- int maximumPoolSize: 线程池中允许的最大线程数;
- long keepAliveTime: 当线程数大于核心时,多余的空闲线程在终止前等待新任务的最长时间
- TimeUnit unit: keepAliveTime参数的时间单位;
- BlockingQueue<Runnable> workQueue: 用于在执行任务之前保存任务的队列。此队列将仅保存
execute
方法提交的Runnable
任务对象; - ThreadFactory threadFactory: 执行器创建新线程时要使用的工厂;
- RejectedExecutionHandler handler: 当达到线程边界和队列容量而阻止执行时要使用的处理程序。
4.3 线程池的状态
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;
ctl
是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息:
- 线程池的运行状态(runState)
- 线程池内有效线程的数量(workerCount)
使用了 Integer 类型来保存,高3位保存 runState
,低29位保存 workerCount
。
线程池的五种状态如下:
- RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
- SHUTDOWN :关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于
RUNNING
状态时,调用shutdown()
方法会使线程池进入到该状态; - STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于
RUNNING
或SHUTDOWN
状态时,调用shutdownNow()
方法会使线程池进入到该状态; - TIDYING :如果所有的任务都已终止了,
workerCount
(有效线程数)为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED
状态; - TERMINATED :在
terminated()
方法执行完后进入该状态,默认terminated()
方法中什么也没有做,进入TERMINATED
的条件如下:- 线程池不是
RUNNING
状态; - 线程池状态不是
TIDYING
状态或TERMINATED
状态; - 如果线程池状态是
SHUTDOWN
并且workerQueue
为空; workerCount
为0;- 设置
TIDYING
状态成功。
- 线程池不是
RUNNING 定义为 -1,SHUTDOWN 定义为 0,其他的都比 0 大,所以等于 0 的时候不能提交任务,大于 0 的话,连正在执行的任务也需要中断。
其转换流程图如下:
4.4 execute方法提交任务
线程池处理任务的流程图:
上面提过,
AbstractExecutorService
抽象类中的 submit
方法中,通过 execute
方法将任务 FutureTask
交给执行器执行,且该抽象类没有实现 execute
方法,由子类实现,下面介绍 ThreadPoolExecutor
对 execute
方法的实现:
- 将来某个时候执行给定任务;
- 该任务可以在新线程或现有池线程中执行;
- 如果由于此执行器已关闭或已达到其容量而无法提交任务以供执行,则该任务将由当前
RejectedExecutionHandler
处理;
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 表示 线程池状态 和 线程数 的整数
int c = ctl.get();
// 当前线程数少于corePoolSize,添加一个worker线程,把当前任务command作为第一个任务
if (workerCountOf(c) < corePoolSize) {
// 添加任务到线程池成功,返回,退出方法
// 第二个参数为true,表示根据corePoolSize作为上界来判断,反之则根据maximumPoolSize来判断
if (addWorker(command, true))
return;
c = ctl.get();
}
// **1** 以上 线程数在 [0, corePoolSize) 之间,无条件开启线程
// 能走到这里有两种情况
// 1.当前线程数大于等于核心线程数
// 2.addWorker失败
// 线程池处于RUNNING状态,并成功把任务添加到任务队列workQueue中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次判断线程池的运行状态
// 如果不是运行状态,移除该command,通过handler使用拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 防止任务提交到队列但是线程都关闭了
else if (workerCountOf(recheck) == 0)
// 第一个参数为null,表示不传firstTask,因为已经放到workQueue中,worker自己去取
addWorker(null, false);
}
// 如果执行到这里,有两种情况:
// 1.线程池已经不是RUNNING状态
// 2.线程池是RUNNING状态,但workerCount>=corePoolSize并且workQueue已满
// 需要继续开启线程直到maximumPoolSize
/**2** 以上 线程数大于等于 corePoolSize,是将任务添加到 workQueue **/
// 第二个参数传入为false,以maximumPoolSize为界创建新的worker,并将提交的任务作为firstTask直接执行
else if (!addWorker(command, false))
// addWorker失败的话,说明
// 1.当前线程数已经达到maximumPoolSize,2.workQueue已满
// 执行拒绝策略
reject(command);
/**3** 以上 workQueue 已满,开启worker线程至 maximumPoolSize,超出则 reject(command) 拒接 **/
}
执行过程还是比较清晰的,分为三步:
- 如果运行的线程少于
corePoolSize
,请尝试以给定 command 作为第一个任务启动新线程; - 工作线程大于等于
corePoolSize
,任务排队,可以成功排队,仍然需要再次检查否应该添加线程; - 如果无法对任务进行排队(队列已满),那么尝试以
maximumPoolSize
为界添加一个新线程,如果失败了,表示已经关闭或饱和,因此拒绝该任务。
4.5 addWorker方法增加处理线程
execute
方法中会使用 addWorker
添加 worker
线程,addWorker
方法也不是无脑添加,而是会有一系列检查。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 线程池状态
int rs = runStateOf(c);
/**
* 下面判断下是否需要新建worker线程,先调整下格式便于理解
*
* if (
* rs >= SHUTDOWN &&
* !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())
* )
* return false;
*
* 我觉得,这个判断可以这样理解:
* 1. rs < SHUTDOWN 的话,线程池是 RUNNING 状态,第一个条件为false,不会执行 return false; 放行
* 2. rs >= SHUTDOWN 按理说是不能新建线程的,但有一种情况除外:
* 调用shutdown()后,不再接受新的任务,但是工作队列的任务还是需要完成的,即第二个条件
* rs == SHUTDOWN(调用shutdown),firstTask == null(不接收新的任务),!workQueue.isEmpty() (队列中还是有任务未完成)
* 这种情况是不会执行 return false;
*
*/
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 自旋尝试
for (;;) {
int wc = workerCountOf(c);
// 如果wc超过CAPACITY,也就是ctl的低29位的最大值,返回false;
// core是该方法的第二个参数,如果为true表示根据corePoolSize作为上界,反之以maximumPoolSize作为上界
// 超过这两类阈值,则不创建worker线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
// 尝试增加workerCount,如果成功,则break retry循环,继续往下并启动线程
break retry;
// 再次获取状态数据,防止多线程竞争,其他线程调用shutdown等方法,导致线程池状态变化
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
// 出现了多线程竞争问题,线程池状态变化了
// 跳过本次retry循环,自旋尝试,重新检查条件,判断是否要 return false;
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 自旋尝试添加线程成功(修改状态成功),开始添加worker线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 根据firstTask来创建Worker对象
w = new Worker(firstTask);
// 每一个Worker对象都会通过threadFactory创建一个线程
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 < SHUTDOWN,即RUNNING状态;
// 2.rs是 SHUTDOWN 状态并且 firstTask 为 null (SHUTDOWN时不让添加新的任务,但还是会执行workQueue中的任务)
// 以上两种情况,则向线程池中添加线程
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 下面会调用 t.start(); 如果t.isAlive(),会报错
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 加到一个 HashSet 中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
// 记录出现过的最大的线程数
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 成功添加并启动了worker线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 没有添加成功,需要回滚状态
addWorkerFailed(w);
}
return workerStarted;
}
4.6 runWorker方法启动worker线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 使用线程工厂创建线程
this.thread = getThreadFactory().newThread(this);
}
Worker
对象实例化的时候,会将自身 this
作为 Runnable
传递给 Thread
对象,说明 addWorker
方法中,最后调用 t.start()
时,会执行 Worker
对象的 run
方法:
public void run() {
// 调用外部类 ThreadPoolExecutor 的 runWorker 方法
runWorker(this);
}
Worker
将 run
方法委托给外部类的 runWorker
方法,并将自身 this
传递作为参数:
runWorker
方法重复从队列中获取任务并执行它们,同时解决以下问题:
- 从
Worker
对象的firstTask
任务开始执行,在这种情况下,我们不需要从队列里获得第一个任务;否则,只要线程池池在运行,我们就可以通过getTask获取任务; - 执行任何任务之前都需要获取锁,同时保证,除非线程池的状态在
STOP
之上,否则,线程的中断标志位不会被设置; - 任务运行之前都会调用
beforeExecute
,如果执行引发异常,会让线程死亡(此时,退出循环时 completedAbruptly=true),而不处理任务; beforeExecute
正常执行后,运行任务,收集其抛出的任何异常通过thrown
传递给afterExecute
方法处理;
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 循环处理任务,如果task为空,则通过getTask来获取任务
while (task != null || (task = getTask()) != null) {
w.lock();
/**
* 这段判断主要是是为了做两件事:
* 1.若线程池的状态在 STOP 之上,则要中断当前 Worker 线程 wt
* 2.反之,线程池的状态为 RUNNING 或 SHUTDOWN,需要保证 wt 不是中断状态(因为还要执行任务),即通过 Thread.interrupted() 清除中断标记位
*
* 理解的难点在于 || && 逻辑运算符的短路特性
*
* 将格式做个调整,方便理解:
* if (
* (runStateAtLeast(ctl.get(), STOP) ||
* (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()
* )
* wt.interrupt(); // 满足上面条件,则中断线程
*
* 1. 若第一个条件为true,|| 后面的就不执行了,短路了,且表示线程池处于 STOP 之上,直接 wt.interrupt(); 给线程中断信号
* 2. 若第一个条件为false,|| 后面第二个条件执行,此时,线程池状态处于 STOP 之下(RUNNING 或 SHUTDOWN),需要保证线程未被中断
* a. Thread.interrupted() 既判断了中断标记,又清除了中断标记位
* b. 需要 recheck 线程状态是否在 STOP 之上(防止多线程竞争,另一个线程调用shutdownNow),同时判断 !wt.isInterrupted(),若未被中断,则中断
*/
// 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 {
task = null;
// 更新处理的任务数
w.completedTasks++;
w.unlock();
}
}
// 什么时候走不到这行代码呢?执行用户任务报错了,throw xxxx,直接进入了下面的 finally 块
// 也就是说,completedAbruptly=true,说明是用户线程抛异常导致线程退出
completedAbruptly = false;
} finally {
// 处理线程退出
processWorkerExit(w, completedAbruptly);
}
}
4.7 getTask方法从队列中获取任务
runWorker
方法的循环体中不断调用 getTask
方法从任务队列中获取任务。
阻塞(blocking)或者超时等待(timed wait)从队列中获取任务;若果出现以下情况,线程必须退出,并返回 null:
- 工作线程数大于
maximumPoolSize
;(调用了setMaximumPoolSize
将maximumPoolSize
调小了); - 线程池调用
shutdownNow()
,状态变成STOP
了; - 线程池调用
shutdown()
,状态变成SHUTDOWN
,并且队列为空; Woker
对象从队列中获取任务超时,并且超时后需要退出线程;(比如,allowCoreThreadTimeOut=true 或者 workerCount > corePoolSize)
private Runnable getTask() {
// 标记上个循环,是否出现获取任务超时的情况
boolean timedOut = false; // Did the last poll() time out?
// 首先检查4种情况,判断是否需要返回null
// compareAndDecrementWorkerCount 可能失败,需要自旋尝试
for (;;) {
int c = ctl.get();
// 上面需要返回null的4中情况中的:情况2、情况3
// 条件1:runStateAtLeast(c, SHUTDOWN)=false,说明是 RUNNING ,跳过,不检查,剩下就是 SHUTDOWN 和 STOP
// 条件2:runStateAtLeast(c, STOP) || workQueue.isEmpty()),即,STOP || (SHUTDOWN && workQueue为空)
// 符合 情况2、情况3,线程数-1,返回 null
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 根据线程池状态判断,当出现了获取任务超时的情况后,是否需要将线程回收
// 两种情况需要回收:
// 1. allowCoreThreadTimeOut=true,核心线程也需要回收,默认为false;
// 2. wc > corePoolSize,超出核心线程数之外的线程需要回收;
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 上面需要返回null的4中情况中的:情况1、情况4
// 条件1:wc > maximumPoolSize,发现线程数居然大于maximumPoolSize了,直接回收
// 条件2:timed && timedOut 根据当前线程池判断后,决定,出现获取任务超时后,需要回收线程(timed=true);上次循环真的出现超时了(timedOut)
// 符合 情况1、情况4,线程数-1,返回 null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 尝试获取任务
try {
// 如果是获取任务超时的情况后需要回收线程的话,调用有超时时间的 poll,到时间就返回 (timed wait)
// 如果是需要阻塞获取任务,调用阻塞的 take,获取不到任务就阻塞 (blocking)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 能走到这,r=null,就是超时了,队列返回了null
// timedOut = true;表示本次获取任务超时了
timedOut = true;
} catch (InterruptedException retry) {
// 获取任务时线程发生了中断,则设置timedOut为false并重试
// 没有标记为超时,单纯地重试
timedOut = false;
}
}
}
4.8 processWorkerExit方法处理worker线程退出
为即将销毁的worker线程进行清理和记录:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 若completedAbruptly为true,则说明线程是异常退出的,workerCount减1
// 正常退出,不需要更新workerCount(getTask方法中会更新,不在这边更新)
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 统计线程池执行的任务数
completedTaskCount += w.completedTasks;
// 从 HashSet 中删除
workers.remove(w);
} finally {
mainLock.unlock();
}
// 根据线程池状态进行判断是否结束线程池
// 若(SHUTDOWN且池和队列为空)或(STOP且池为空),则转换为TERMINATED状态
// 若可以终止,但workerCount为非零,则中断空闲的worker线程
// 必须在执行可能导致终止的任何操作(减少工作人员计数或在关闭期间从队列中删除任务)后调用此方法
tryTerminate();
/*
* 当线程池是RUNNING或SHUTDOWN状态时
* 1. 如果worker是异常结束,那么 addWorker 替换一个线程
* 2. getTask无任务,正常结束
* a. 如果allowCoreThreadTimeOut=true,并且队列有任务,至少保留一个worker继续执行
* b. 如果allowCoreThreadTimeOut=false,保留workerCount不少于corePoolSize
*/
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);
}
}
4.9 reject方法执行拒绝策略
对提交的任务,执行拒绝操作,上文 execute
方法中有两处调用。
private volatile RejectedExecutionHandler handler;
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
这个 handler
就是在构造线程池的时候传入的,它是 RejectedExecutionHandler
的实例,RejectedExecutionHandler
在 ThreadPoolExecutor
中有四个实现类:
CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
// 线程池未关闭的情况下,由调用者线程执行任务r
// 线程池关闭,任务将被丢弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
AbortPolicy
public static class AbortPolicy implements RejectedExecutionHandler {
// 直接抛出 RejectedExecutionException 异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
DiscardPolicy
public static class DiscardPolicy implements RejectedExecutionHandler {
// 什么也不做,即丢弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
// 获取并忽略执行器将执行的下一个任务,即在队列中等待最久的
// 然后重试任务r的执行
// 线程池关闭,任务将被丢弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
4.10 shutdown、shutdownNow关闭线程池
shutdown:有序关闭线程池,执行先前提交的任务,但不接受新任务。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 切换状态为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲线程
interruptIdleWorkers();
// 钩子,空方法,子类实现
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
shutdownNow:尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表,返回时,这些任务将从任务队列中移除。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 切换状态为STOP
advanceRunState(STOP);
// 中断所有工作线程,无论是否空闲
interruptWorkers();
// 取出队列中没有被执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
// 返回被执行的任务
return tasks;
}
5 异常处理
5.1 线程池怎么把异常吞了?
以上代码中,通过线程池
submit
方法,提交一段会抛异常的的代码逻辑,发现由于除0异常,没有打印出 "finish task",但是也没有发现异常抛出,这种隐藏的问题常常很难排查;
将 submit
替换为 execute
方法,再执行一遍,异常成功抛出:
上文提到,线程池中每个
worker
线程都有 thread 属性绑定运行线程,Runnable
类型的 firstTask
属性绑定执行的任务:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// 每个 worker 绑定一个 thread,用于执行任务
final Thread thread;
// 这里的 Runnable 是任务的意思,即task或command。
// 如果创建线程的时候指定了firstTask,线程启动时需要执行的第一个任务
// firstTask指定null的话,线程启动后,自己到任务队列中取任务
Runnable firstTask;
...
}
submit
和 execute
的区别在于:
submit
会将提交的Runnable
对象通过newTaskFor
包装成FutureTask
作为task
,然后调用execute
提交到线程池;execute
直接将 普通Runnable
对象作为 task 提交到线程池;- 线程池执行到该
task
时,调用task.run()
执行逻辑,执行过程在runWorker
方法中; submit
提交的任务调用的是FutureTask.run();``submit
提交的任务调用的是普通Runnable.run();
从
runWorker
方法可以猜测,普通 Runnable
任务,出现异常是可以抛出的;submit
提交的任务会被线程池吞掉异常,是不是因为 FutureTask
会吞掉异常呢?
通过分析
FutureTask
的 run
方法,发现确实是这个原因,submit
提交的任务出现异常,需要通过 Future.get()
获取。
5.2 线程池处理执行异常的一般方法
任务内部显式try-catch
executorService.submit(() -> {
try {
int x = 1/0;
} catch (Exception e) {
// 处理异常
System.out.println(e);
}
});
实现Thread.UncaughtExceptionHandler
UncaughtExceptionHandler
是 Thread
类的一个内部接口,表示当线程由于未捕获的异常而突然终止时调用的处理程序。当线程由于未捕获的异常而即将终止时,Java虚拟机将使用getUnaughtExceptionHandler
查询线程的 UnaughtException
处理程序,并调用处理程序的 uncaughtException
方法,将线程和异常作为参数传递。
Thread.UncaughtExceptionHandler sceneHandler = (t, e) -> {
// 仅举例, 不同场景各不相同
System.out.println(e);
};
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler(sceneHandler)
.setDaemon(true).setNameFormat("pool-thread").build();
ThreadPoolExecutor executorService =
new ThreadPoolExecutor(1, 3, 1000, TimeUnit.HOURS, new LinkedBlockingQueue(5), threadFactory);
pool.execute(() -> {
int i = 1 / 0;
});
executorService.shutdown();
重写线程池的afterExecute
ThreadPoolExecutor executorService =
new ThreadPoolExecutor(1, 3, 1000, TimeUnit.HOURS, new LinkedBlockingQueue(5)) {
@Override
public void afterExecute(Runnable r, Throwable t) {
// 处理异常
System.out.println(t);
}
};
executorService.execute(() -> {
int a = 1/0;
});
executorService.shutdown();
调用Future.get获取异常
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<?> future = executorService.submit(() -> {
int a = 1 / 0;
});
try {
future.get();
} catch (Exception e) {
// 处理异常
System.out.println(e);
}
6 阻塞队列
- ArrayBlockingQueue:一个用数组实现的有界阻塞队列,按FIFO排序;
- LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,newFixedThreadPool线程池使用了这个队列;
- DelayQueue:是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序,newScheduledThreadPool线程池使用了这个队列;
- PriorityBlockingQueue:具有优先级的无界阻塞队列;
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,newCachedThreadPool线程池使用了这个队列。
7 线程池监控
- getTaskCount:线程池已经执行的和未执行的任务总数;
- getCompletedTaskCount:线程池已完成的任务数量,该值小于等于taskCount;
- getLargestPoolSize:线程池曾经创建过的最大线程数量,通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize;
- getPoolSize:线程池当前的线程数量;
- getActiveCount:当前线程池中正在执行任务的线程数量;
- 可以扩展 ThreadPoolExecutor类的几个空方法,如beforeExecute方法,afterExecute方法和terminated方法,这些方法在执行前或执行后增加一些新的操作,例如统计线程池的执行任务的时间等。