1. 线程池相关的类
线程池相关的类都位于java.util.concurrent包下。主要的类的类图如下:
1.1. Executor
Executor 接口是所有线程池类的最上层的基类。Executor 接口虽然只是简单的定义了一个提交任务的 execute 方法,但它却表达了线程池的一个最基本的思想:将任务的执行和提交解耦开来。 Executor 中用 Runnable 来表示异步任务。
使用者将需要执行的任务提交到 Executor,Executor 的线程池会负责发起任务的执行,对外屏蔽了任务执行的细节。
package java.util.concurrent;
public interface Executor {
/**
* 提交任务并在未来某个时刻执行任务
* Throws: RejectedExecutionException - 若提交的任务不被接受
* NullPointerException – 如果command为null
*/
void execute(Runnable command);
}
1.2. ExecutorService
Executor 接口过于简单了,一个线程池还包含很多其它的东西。ExecutorService 接口继承自 Executor 接口,它扩展了 Executor 线程池框架的能力。
1.2.1. 提交 Callable 和返回 Future
在 Java 中使用 Runnable 来表示异步任务而非 Thread。Thread 更多代表一个线程,即包含了异步任务也包含了执行任务的线程、以及线程间同步的方法。
Executor 只能提交 Runnable 的任务,但异步任务往往都是有结果的,所以 Java 5 开始引入了 Callable 表示有结果返回或可能抛出受检查的异常的异步任务。其定义如下:
public interface Callable {
V call() throws Exception;
}
而 ExecutorService 接口中提供了提交 Callable 任务的方法。
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
}
同时并不是将异步任务提交到线程池后并就不管不顾了,我们可能需要知道任务是否执行完成了并获取到任务的执行结果、或是取消任务。Java 提供了 Future 表示一个提交到线程池的异步任务的‘异步结果’。而 ExecutorService 接口中提交异步任务的方法都是会返回任务 Future。
public interface ExecutorService extends Executor {
Future submit(Callable task);
// 返回的Future的get方法将返回指定的result
Future submit(Runnable task, T result);
// 返回的Future的get方法将返回null
Future submit(Runnable task);
}
1.2.2. 关闭线程池
当不再需要线程池时应该及时将其关闭,以回收线程池中的线程和其它线程池可能占用的资源。ExecutorService 中提供了关闭线程池的方法 shutdown 和 shutdownNow 以及查询关闭状态的相关方法:
public interface ExecutorService extends Executor {
void shutdown();
List shutdownNow();
// 线程池是否已经关闭
boolean isShutdown();
// 在调用shutdown或shutdownNow后,是否所有的任务都已经执行完成
boolean isTerminated();
// 等待线程池内所有线程都关闭
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
}
shutdown 方法会有序的关闭线程池,调用后线程池将不再接受新额度任务,但已提交还未执行的任务还是会被执行。 如果已经关闭,则多次调用不会有没有其它影响。
shutdownNow 方法尝试停止所有任务,已提交还未执行的任务不会被执行,并会对执行中的任务发出中断请求。该方法会返回所有已提交但未执行的任务。
1.2.3. 批量提交任务
ExecutorService 还提供了批量提交任务并同步等待任务执行的方法:
/*
* 执行所有给定的Callable的任务,并等待所有任务执行完成.
* 执行完成后返回这些任务的Future列表,顺序与任务列表的顺序相同。
* 返回的每一个Future的Future.isDone都为true。
*/
List> invokeAll(Collection> tasks) throws InterruptedException;
/*
* 执行所有给定的Callable的任务,并等待指定时间直到所有任务执行完成或者到达超时时间。
* 返回时将返回所有完成的任务的Future列表,未完成的任务被取消。
*/
List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException;
/*
* 执行所有给定的Callable的任务,并等待所有任务执行完成.
* 执行完成后返回这些任务的Future列表,顺序与任务列表的顺序相同。
* 返回的每一个Future的Future.isDone都为true。
*/
List> invokeAll(Collection> tasks) throws InterruptedException;
/*
* 执行所有给定的Callable的任务,并等待指定时间直到所有任务执行完成或者到达超时时间。
* 返回时将返回所有完成的任务的Future列表,未完成的任务被取消。
*/
List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException;
invokeAll 用于提交一批Callable任务,并同步等待任务执行结束,并返回所有任务的Future。invokeAll 方法提交的任务执行顺序与任务集合中元素的顺序可能不一致,这取决于底层线程池的实现方式。
ExecutorService 还提供了invokeAny方法,用于提交一批任务,并等待其中任意一个执行结束。invokeAny 正常或异常返回时未执行的任务会被取消。
1.3. AbstractExecutorService
ExecutorService 接口完善了 Executor 框架接口的能力。但生成任务的 Future 和批量提交并等待的方法的还是比较通用的,没必要所有 ExecutorService 的实现都从头去实现这些操作,具体的线程池实现应当聚焦于为异步任务分配线程并执行的细节。
所以 Java 提供了抽象类 AbstractExecutorService,其中提供了通用成任务的 Future 和批量提交的方法的实现,通过继承 AbstractExecutorService 实现我们的线程池时,我们只需要关注于 execute 方法的实现。
1.3.1. 为任务生成 Future
AbstractExecutorService 实现了三个可返回 Future 的提交任务方法,它们的实现都是将任务封装为 RunnableFuture 后提交到 execute 方法并返回 RunnableFuture 实例。
不过 AbstractExecutorService 并未实现 execute 方法,execute 方法中任务提交的细节需要我们自己实现。
AbstractExecutorService 提交任务时生成的 RunnableFuture 具体类型为 FutureTask,它是 Future 接口的一个具体实现,FutureTask 可以执行异步任务和等待任务执行结束,详细可以参考:juejin.cn/post/724844…。
其实现如下:
newTaskFor 统一为任务生成 FutureTask:
protected RunnableFuture newTaskFor(Runnable runnable, T value) {
return new FutureTask(runnable, value);
}
1.3.2. 等待批量提交的任务的结果
invokeAll 用于批量提交任务并等待所有任务都执行完成然后返回这些任务的 Future,也可以指定等待任务执行结束的超时时间。
AbstractExecutorService 实现了 invokeAll,它通过 newTaskFor 为所有提交的任务生成对应 FutureTask 提交到 execute 去执行,然后通过逐个调用各个任务的 FutureTask 的 get 等待任务执行完成。若是指定超时,则通过调用 FutureTask 的带超时参数的 get 方法来每个任务都最大等待指定超时时间。
2. ThreadPoolExecutor 的实现
2.1. 线程池状态
ThreadPoolExecutor 线程池有自己的生命周期,线程池所处生命周期状态不同,可以对其发起的操作也不同,其生命周期内包括以下状态:
- RUNNING:运行态,线程池的初始状态。该状态下可以接受新提交的任务,阻塞队列中的任务也会正常取出执行。
- SHUTDOWN:关闭状态,在调用 shutdown 后进入该状态。该状态下不再接受新提交的任务,但队列中的任务还是会被执行。
- STOP:停止状态,在调用 shutdownNow 后进入该状态。该状态下不再接受新提交的任务,同时即使阻塞队列中有任务也不会被执行。
- TIDYING:终止状态,shutdown 或 shutdownNow 调用后线程池中没有任务要执行且工作线程全部被关闭后进入该状态。
- TERMINATED:线程池进入到 TIDYING 状态之后会调用 terminated 方法,该方法在 ThreadPoolExecutor 中为一个空方法,留给大家扩展 ThreadPoolExecutor 去按需实现。调用完 terminated 方法后线程池进入到 TERMINATED 终态。
状态更新流程图如下:
ThreadPoolExecutor 线程池的生命周期状态只能‘前进’不能‘后退’。
除了线程池的生命周期状态,ThreadPoolExecutor 还包括一个重要的的状态即当前工作线程数,并且当前工作线程数和生命周期状态息息相关的,例如 RUNNING 状态下才可提交新任务,也就是只有在 RUNNING 状态下工作线程数才能增加,同时只有工作线程数为 0 是才可进入到 TIDYING 状态。
为了保证生命周期状态和工作线程数的一致性,ThreadPoolExecutor 使用一个 AtomicInteger 类型的字段保存了这两个状态,这样减少了使用两个变量去保存时为保证两者一致性而需要额外的同步操作。
该字段的变量名为 ctl,ctl 变量的高 3 位保存线程池状态,低 29 位保存工作线程数。ctl 的更新都是通过 AtomicInteger.compareAndSet 方法提供的无锁的 CAS 去更新的,保证了效率和一致性。
public class ThreadPoolExecutor extends AbstractExecutorService {
// 线程状态和当前工作线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 获取状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取当前工作线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
// 使用指定的状态和工程线程数生成一个新的ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 工作线程数加1
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
// 工作线程数减1
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
}
初始化时线程池状态为 RUNNING,工作线程数为 0。
2.2. 线程管理
线程池中的线程生命周期大概为:新任务被提交时可能会创建一个新的线程执行任务,任务执行完成后线程继续从队列中获取任务来执行,直到无法从队列中取得任务时才退出。
这只是一个大概的生命周期,这其中每个环境都有许多细节,例如提交任务时并一定都会创建新线程去执行任务、从任务队列中获取任务时包含了线程结束的判断,即如果线程需要终止则不返回任务给它、获取不到任务线程结束前还有一些状态需要同步修改。这些细节后面会按阶段逐个分析。
2.2.1. 新线程的创建
当有任务提交时,会判断是否需要创建新的线程来执行任务,若需要则创建,判断规则如下:
- 若当前线程数小于核心线程池数,则直接创建一个新线程来执行该任务。
- 若当前线程数已大于等于核心线程数,则将任务添加到任务队列中。
- 若任务队列已满且当前线程数未达到最大线程数,则创建一个新线程来执行任务。
核心线程数和最大线程数通过构造函数中的 corePoolSize 和 maximumPoolSize 参数指定。
流程图如下:
当然创建新线程或是将任务添加到任务队列中需要线程池状态在 RUNNING 中才可进行,否则任务将被拒绝并交给 RejectedExecutionHandler。
这些都在 ThreadPoolExecutor 的 execute 方法中实现,ThreadPoolExecutor 继承自 AbstractExecutorService, AbstractExecutorService 已经实现了 submit,其会为提交的 Runnable 和 Callable 任务生成 FutureTask,然后调用 execute 来执行,所以 ThreadPoolExecutor 中只需要专注实现 execute 方法。
execute 的实现如下:
可看到创建新的线程并用新的线程发起任务的执行主要由 addWorker 提供实现。
在 addWorker 方法中首先确保线程池状态为RUNNING,否则无法创建新的线程。但也有例外,线程池 shutdown 后任务队列中的任务还是要执行完的,为避免线程池中的线程都被关闭了而无线程执行队列中的任务,所以 shutdown 后若线程因异常而退出时还是会创建新的线程补齐线程,对于这种情况即使状态非 RUNNING 也是可以创建新线程的,不过这时 firstTask 是为 null 的。
校验了状态后还好再次校验线程数,如时核心线程则要求当前线程数小于 corePoolSize,否则要求线程数小于 maximumPoolSize。然后再通过 CAS 将当前线程数加1,即先修改状态再创建线程。
状态校验通过后就会创建一个表示工作线程的 Worker 实例,并发起线程的执行。发起线程执行前会将 Worker 实例添加到记录当前存活工作线程列表 workers 中,同时会更新记录了线程池最多存活工作线程数 largestPoolSize。workers 列表和 largestPoolSize 都是非线程安全的,所以需要先获取到 mainLock 锁。
public class ThreadPoolExecutor extends AbstractExecutorService {
// 线程池只唯一的显示的锁,用于非线程安全的状态的更新
private final ReentrantLock mainLock = new ReentrantLock();
// 当前存活的工作线程
private final HashSet workers = new HashSet();
// 历史最多存活工作线程数
private int largestPoolSize;
}
创建 Worker 实例并启动线程的实现如下:
若线程池状态发生了变化当前非 Running 或者线程非 alive 的则线程不会被启动,但之前当前线程数已经加1了,并且也有可能线程添加到 workers 中了但是未被启动,这些情况下就需要调用 addWorkerFailed 清理这些错误的状态,在 addWorkerFailed 主要是将 Worker 实例从 workers 移除并将当前线程数减1。实现如下:
创建好工作线程 Worker 实例并启动后,剩下的就交给 Worker ‘自由’发挥了。
2.2.2. Worker
Worker 类是 ThreadPoolExecutor 中的一个内部类,它实现了 Runnable 接口并持有线程,代表一个可以执行异步任务的工作线程。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
// 正在的工作线程实例
final Thread thread;
// 若是提交任务时创建的工作线程,firstTask即为当时提交的任务
Runnable firstTask;
// 记录工作线程执行了多少个任务
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 通过线程工厂创建线程,线程的异步任务就是Worker自身
this.thread = getThreadFactory().newThread(this);
}
}
当向 ThreadPoolExecutor 中提交任务时,若可以直接未任务创建一个新的线程来运行改任务则就会创建一个 Worker 实例来执行该任务,此时 firstTask 即为提交的任务。当然并非只有在提交任务时才会创建新的线程,工作线程在执行任务时因异常而异常退出时会创建一个新的线程补全异常退出的线程,这时 firstTask 即为null。
Worker 实例创建好后会校验一下线程池当前状态是否在 RUNNING 中或者是因有线程异常退出而创建新线程来补充。状态校验通过后就会发起 Worker 中 thread 的执行。
Worker 虽然实现了 Runnable 接口,但它的执行最终是调用的 ThreadPoolExecutor 类中的 runWorker 方法。Worker 是 ThreadPoolExecutor 的内部类,共享 ThreadPoolExecutor 的状态和方法,所以可直接调用 runWorker 方法。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
public void run() {
runWorker(this);
}
}
2.2.3. 线程的执行
在 runWorker 方法中执行时,若 firstTask 不为空则先执行 firstTask,在 firstTask 执行完成后 runWorker 方法并不会退出也是时线程还不会结束,而是会继续从任务队列中获取任务来执行,直到从队列中获取不到任务时线程才退出。若 firstTask 未空则会直接开始从任务队列中获取任务来执行。实现如下:
2.2.4. 线程的结束
从上面可以看出当线程从任务队列中获取到一个 null 时和任务抛出异常时会结束 while 循环,循环结束也就意味则线程将被结束。
先看一下何时任务队列会返回一个 null,也就是线程在何时正常结束。getTask 方法实现了从任务队列中获取任务,其不仅负责从任务队列中获取任务,还负责判断当前获取任务的工作线程是否应该结束,若是则返回 null。以下三种情况线程需要结束,即 getTask 需要返回 null。
1. 线程池已被关闭
通过 shutdown 关闭线程池后,其状态为 SHUTDOWN,这时若任务队列中的任务已经被执行完了则工作线程也就没用了,这时工作可以结束并将当前工作线程数减 1,然后 getTask 返回 null 给工作线程。
若是通过 shutdownNow 关闭线程池后,其状态为 STOP,此时任务队列中即使还有任务也不会再执行,工作可以结束并将当前工作线程数减 1,然后 getTask 返回 null 给工作线程。其实现如下:
private Runnable getTask() {
for (;;) {
int c = ctl.get();
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
...
}
...
}
2. 线程已空闲超时
提交任务时若当前线程数已经到达或超过 corePoolSize 并且任务队列也满了,这时若线程池中总的线程数没有超过 maximumPoolSize 的话还是可以创建新的线程来执行任务的。但这样线程数就超过了可以一直存活的线程数 corePoolSize,所以当线程空闲超过指定时间时应该将多的空闲线程结束。
也可以通过 allowCoreThreadTimeOut 设置即使当前线程数没有到达 corePoolSize 但线程空闲超时了也进行回收。
工作线程执行完直接提交的任务后会从任务队列中获取任务去执行,取任务时若可以超时结束则会调用阻塞的任务队列带超时参数的 poll 方法,poll 的超时参数值即为线程池构造方法中指定的空闲超时时间,若在超时时间内没有取到任务则线程空闲超时,getTask 返回null。若当前没有到空闲超时回的收条件则调用任务队列的 take 方法获取任务。
private Runnable getTask() {
for (;;) {
// 当前线程数是否超过corePoolSize或coreThread也空闲超时回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
// 已空闲超时则返回null从而使线程结束
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;
}
}
}
3. 异常退出
在 runWorker 方法会发起任务的执行,执行任务的 run 方法时抛出的任务异常都将被捕获并重新抛出,重新抛出异常时也就会退出获取任务的 while 循环,从而任务也将被终止。
final void runWorker(Worker w) {
...
// 是否因异常而终止
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != 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);
}
...
completedAbruptly = false;
} finally {
// 线程结束后的清理操作
processWorkerExit(w, completedAbruptly);
}
}
4. 线程退出清理
但线程执行结束时,无论是正常的结束还是异常结束,在线程正式退出前还有一些状态要更新,包括:
- 当前工作线程数减1
- 将 Worker 实例从 workers 列表中移除
- 将线程执行完成的任务数累加到线程池完成的任务计数中
若是异常退出,但若退出时线程池还处于RUNNING或 SHUTDOWN 状态,则说明线程池还有任务需要执行,所以此时会再新建一个线程补充上异常退出的线程。这时新建的线程 firstTask 为null,启动执行是该线程直接从任务队列中去取任务。
若是正常退出也要判断一下是否当前线程数小于了 corePoolSize, 或者允许核心线程空闲超时退出但若退出后线程池就没有任务但队列中还有任务则至少要保留一个线程。这两种情况即使是正常退出下也会在创建一个新的线程去补充退出的线程。其实现如下:
2.3. 关闭线程池
shutdown 和 shutdownNow 两个关闭线程池的方法。
若是 shutdown 方法关闭,则先将状态改为 SHUTDOWN ,然后再将对当前所有空闲的工作线程发送中断请求。提交任务时若线程池状态为 SHUTDOWN 则将不会结束新提交的任务。从队列中获取任务时若线程池状态为 SHUTDOWN 同时任务队列为空则会返回一个 null 任务给线程,从而终止线程的执行。
若是调用的 shutdownNow 方法关闭,则先将状态改为 STOP ,然后再将对所有工作线程发送中断请求,最后将任务队列的任务全部移除并返回。同样提交任务时若线程池状态为 STOP 则将不会接受新提交的任务。与 shutdown 方法调用后不同的是,但工作线程充任务队列中获取任务时若状态 STOP 则会返回一个 null 任务给线程,从而终止线程的执行,无论队列是否还有任务。
tryTerminate 的方法会判断线程池中的状态是否已经为 SHUTDOWN 或 STOP了,若是同时工作线程数也0了,则线程池将更新为最终的关闭状态 TERMINATED。若是调用的 shutdown 方法关闭则还需要任务队列为空。
tryTerminate方法调用的地方有,在 shutdown 或 shutdownNow 方法中、线程结束时、启动新线程失败时、提交任务但线程池状态非 RUNNING 而尝试将任务从工作队列中移除时。