1、前言
在日常的开发调试中,我们经常会直接new一个Thread对象来执行某个任务。这种方式在任务数较少的情况下比较简单实用,但是在并发量较大的场景中却有着致命的缺陷。例如在访问量巨大的网站中,如果每个请求都开启一个线程来处理的话,即使是再强大的服务器也支撑不住。CPU资源是有限的,在CPU较为空闲的情况下,新增线程可以提高CPU的利用率,达到提升性能的效果。但是在CPU满载运行的情况下,再继续增加线程不仅不能提升性能,反而因为线程的竞争加大而导致性能下降,甚至导致服务器宕机。因此,在这种情况下我们可以利用线程池来使线程数保持在合理的范围内,使得CPU资源被充分的利用,且避免因过载而导致宕机的危险。在Executors中为我们提供了多种静态工厂方法来创建各种特性的线程池,其中大多数是返回ThreadPoolExecutor对象。
先看一下继承关系:
最顶层Executor接口:
public interface Executor { void execute(Runnable command); }
可以如此使用这个接口
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
如果我们希望线程池同步执行每一个任务,我们可以这么实现这个接口:
class DirectExecutor implements Executor
{
public void execute(Runnable r)
{
r.run();// 这里不是用的new Thread(r).start(),也就是说没有启动任何一个新的线程。
}
}
我们希望每个任务提交进来后,直接启动一个新的线程来执行这个任务,我们可以这么实现:
class ThreadPerTaskExecutor implements Executor
{
public void execute(Runnable r)
{
new Thread(r).start(); // 每个任务都用一个新的线程来执行
}
}
我们再来看下怎么组合两个 Executor 来使用,下面这个实现是将所有的任务都加到一个 queue 中,然后从 queue 中取任务,交给真正的执行器执行,这里采用 synchronized 进行并发控制:
class SerialExecutor implements Executor
{
// 任务队列
final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
// 这个才是真正的执行器
final Executor executor;
// 当前正在执行的任务
Runnable active;
// 初始化的时候,指定执行器
SerialExecutor(Executor executor)
{
this.executor = executor;
}
// 添加任务到线程池: 将任务添加到任务队列,scheduleNext 触发执行器去任务队列取任务
public synchronized void execute(final Runnable r)
{
tasks.offer(new Runnable()
{
public void run()
{
try
{
r.run();
}
finally
{
scheduleNext();
}
}
});
if (active == null)
{
scheduleNext();
}
}
protected synchronized void scheduleNext()
{
if ((active = tasks.poll()) != null)
{
// 具体的执行转给真正的执行器 executor
executor.execute(active);
}
}
}
当然了,Executor 这个接口只有提交任务的功能,太简单了,我们想要更丰富的功能,比如我们想知道执行结果、我们想知道当前线程池有多少个线程活着、已经完成了多少任务等等,这些都是这个接口的不足的地方。接口ExecutorService继承了Executor接口,并增加了submit、shutdown、invokeAll等等一系列方法。一般使用的也是这个接口。实现如下:
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InteruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<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;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
一般我们定义一个线程池的时候,往往都是使用这个接口:
ExecutorService executor = Executors.newFixedThreadPool(args...);
ExecutorService executor = Executors.newCachedThreadPool(args...);
因为这个接口中定义的一系列方法大部分情况下已经可以满足我们的需要了。
AbstractExecutorService抽象类实现了ExecutorService接口,并且提供了一些方法的默认实现,例如submit方法、invokeAny方法、invokeAll方法。
像execute方法、线程池的关闭方法(shutdown、shutdownNow等等)就没有提供默认的实现。
public abstract class AbstractExecutorService implements ExecutorService {
// RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask
// 下面两个 newTaskFor 方法用于将我们的任务包装成 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);
}
// 提交任务
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 2. 交给执行器执行,execute 方法由具体的子类来实现
// 前面也说了,FutureTask 间接实现了Runnable 接口。
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
// 此方法目的:将 tasks 集合中的任务提交到线程池执行,任意一个线程执行完后就可以结束了
// 第二个参数 timed 代表是否设置超时机制,超时时间为第三个参数,
// 如果 timed 为 true,同时超时了还没有一个线程返回结果,那么抛出 TimeoutException 异常
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
// 任务数
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
//
List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
// ExecutorCompletionService 不是一个真正的执行器,参数 this 才是真正的执行器
// 它对执行器进行了包装,每个任务结束后,将结果保存到内部的一个 completionQueue 队列中
// 这也是为什么这个类的名字里面有个 Completion 的原因吧。
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
try {
// 用于保存异常信息,此方法如果没有得到任何有效的结果,那么我们可以抛出最后得到的一个异常
ExecutionException ee = null;
long lastTime = timed ? System.nanoTime() : 0;
Iterator<? extends Callable<T>> it = tasks.iterator();
// 首先先提交一个任务,后面的任务到下面的 for 循环一个个提交
futures.add(ecs.submit(it.next()));
// 提交了一个任务,所以任务数量减 1
--ntasks;
// 正在执行的任务数(提交的时候 +1,任务结束的时候 -1)
int active = 1;
for (;;) {
// ecs 上面说了,其内部有一个 completionQueue 用于保存执行完成的结果
// BlockingQueue 的 poll 方法不阻塞,返回 null 代表队列为空
Future<T> f = ecs.poll();
// 为 null,说明刚刚提交的第一个线程还没有执行完成
// 在前面先提交一个任务,加上这里做一次检查,也是为了提高性能
if (f == null) {
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
// 这里是 else if,不是 if。这里说明,没有任务了,同时 active 为 0 说明
// 任务都执行完成了。其实我也没理解为什么这里做一次 break?
// 因为我认为 active 为 0 的情况,必然从下面的 f.get() 返回了
// 2018-02-23 感谢读者 newmicro 的 comment,
// 这里的 active == 0,说明所有的任务都执行失败,那么这里是 for 循环出口
else if (active == 0)
break;
// 这里也是 else if。这里说的是,没有任务了,但是设置了超时时间,这里检测是否超时
else if (timed) {
// 带等待的 poll 方法
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
// 如果已经超时,抛出 TimeoutException 异常,这整个方法就结束了
if (f == null)
throw new TimeoutException();
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
// 这里是 else。说明,没有任务需要提交,但是池中的任务没有完成,还没有超时(如果设置了超时)
// take() 方法会阻塞,直到有元素返回,说明有任务结束了
else
f = ecs.take();
}
/*
* 我感觉上面这一段并不是很好理解,这里简单说下。
* 1. 首先,这在一个 for 循环中,我们设想每一个任务都没那么快结束,
* 那么,每一次都会进到第一个分支,进行提交任务,直到将所有的任务都提交了
* 2. 任务都提交完成后,如果设置了超时,那么 for 循环其实进入了“一直检测是否超时”
这件事情上
* 3. 如果没有设置超时机制,那么不必要检测超时,那就会阻塞在 ecs.take() 方法上,
等待获取第一个执行结果
* 4. 如果所有的任务都执行失败,也就是说 future 都返回了,
但是 f.get() 抛出异常,那么从 active == 0 分支出去(感谢 newmicro 提出)
// 当然,这个需要看下面的 if 分支。
*/
// 有任务结束了
if (f != null) {
--active;
try {
// 返回执行结果,如果有异常,都包装成 ExecutionException
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}// 注意看 for 循环的范围,一直到这里
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
// 方法退出之前,取消其他的任务
for (Future<T> f : futures)
f.cancel(true);
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
// 执行所有的任务,返回任务结果。
// 先不要看这个方法,我们先想想,其实我们自己提交任务到线程池,也是想要线程池执行所有的任务
// 只不过,我们是每次 submit 一个任务,这里以一个集合作为参数提交
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
// 这个很简单
for (Callable<T> t : tasks) {
// 包装成 FutureTask
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
// 提交任务
execute(f);
}
for (Future<T> f : futures) {
if (!f.isDone()) {
try {
// 这是一个阻塞方法,直到获取到值,或抛出了异常
// 这里有个小细节,其实 get 方法签名上是会抛出 InterruptedException 的
// 可是这里没有进行处理,而是抛给外层去了。此异常发生于还没执行完的任务被取消了
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
// 这个方法返回,不像其他的场景,返回 List<Future>,其实执行结果还没出来
// 这个方法返回是真正的返回,任务都结束了
return futures;
} finally {
// 为什么要这个?就是上面说的有异常的情况
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
}
// 带超时的 invokeAll,我们找不同吧
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null || unit == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
long lastTime = System.nanoTime();
Iterator<Future<T>> it = futures.iterator();
// 每提交一个任务,检测一次是否超时
while (it.hasNext()) {
execute((Runnable)(it.next()));
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
// 超时
if (nanos <= 0)
return futures;
}
for (Future<T> f : futures) {
if (!f.isDone()) {
if (nanos <= 0)
return futures;
try {
// 调用带超时的 get 方法,这里的参数 nanos 是剩余的时间,
// 因为上面其实已经用掉了一些时间了
f.get(nanos, TimeUnit.NANOSECONDS);
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
} catch (TimeoutException toe) {
return futures;
}
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
}
done = true;
return futures;
} finally {
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
}
}
这个抽象类包装了一些基本的方法,可是像 submit、invokeAny、invokeAll 等方法,它们都没有真正开启线程来执行任务,它们都只是在方法内部调用了 execute 方法,所以最重要的 execute(Runnable runnable) 方法还没出现,需要等具体执行器来实现这个最重要的部分,这里我们要说的就是 ThreadPoolExecutor 类了。
2、ThreadPoolExecutor
ThreadPoolExecutor 是 JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它实现了任务提交、线程管理、监控等等方法。可以基于它来进行业务上的扩展,以实现我们需要的其他功能,比如实现定时任务的类 ScheduledThreadPoolExecutor 就继承自 ThreadPoolExecutor。
2.1线程池状态和线程数目
//高3位表示线程池状态, 后29位表示线程个数,记录线程池状态和线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
////线程数量统计位数29 Integer.SIZE=32
private static final int COUNT_BITS = Integer.SIZE - 3;
//容量 000 11111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//运行状态 例:11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//关闭状态 例:00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//停止状态 例:00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//整理状态 例:01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//终止状态 例:01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
//获取运行状态(获取前3位)
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程个数(获取后29位)
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc;
RUNNING = 0:接受新任务并且处理阻塞队列里的任务
SHUTDOWN = 1:拒绝新任务但是处理阻塞队列里的任务
STOP = 2:拒绝新任务并且抛弃阻塞队列里的任务同时会中断正在处理的任务
TIDYING :所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为0,将要调用terminated方法
TERMINATED = 3:终止状态。terminated方法调用完成以后的状态
线程池状态转换:
线程池也有自己的生命周期,从创建到终止,线程池在每个阶段所做的事情是不一样的。新建一个线程池时它的状态为Running,这时它不断的从外部接收并处理任务,当处理不过来时它会把任务放到任务队列中;之后我们可能会调用shutdown()来终止线程池,这时线程池的状态从Running转为Shutdown,它开始拒绝接收从外部传过来的任务,但是会继续处理完任务队列中的任务;我们也可能调用shutdownNow()来立刻停止线程池,这时线程池的状态从Running转为Stop,然后它会快速排空任务队列中的任务并转到Tidying状态,处于该状态的线程池需要执行terminated()来做相关的扫尾工作,执行完terminated()之后线程池就转为Terminated状态,表示线程池已终止。
那么ThreadPoolExecutor是怎样存放状态信息和线程数信息的。ThreadPoolExecutor利用原子变量ctl来同时存储运行状态和线程数的信息,其中高3位表示线程池的运行状态(runState),后面的29位表示线程池中的线程数(workerCount)。上面代码中,runStateOf方法是从ctl取出状态信息,workerCountOf方法是从ctl取出线程数信息,ctlOf方法是将状态信息和线程数信息糅合进ctl中。具体的计算过程如下图所示。
2.2、关键成员变量
//任务队列
private final BlockingQueue<Runnable> workQueue;
//工作者集合
private final HashSet<Worker> workers = new HashSet<Worker>();
//线程达到的最大值
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();
2.3 核心构造函数
//核心构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) {
throw new IllegalArgumentException();
}
if (workQueue == null || threadFactory == null || handler == null) {
throw new NullPointerException();
}
this.corePoolSize = corePoolSize; //设置核心线程数量
this.maximumPoolSize = maximumPoolSize; //设置最大线程数量
this.workQueue = workQueue; //设置存放任务队列
this.keepAliveTime = unit.toNanos(keepAliveTime); //设置非核心线程存活时间
this.threadFactory = threadFactory; //设置用来生产线程的工厂
this.handler = handler; //设置不能再放入任务时候执行的拒绝策略
}
ThreadPoolExecutor有多个构造器,所有的构造器都会调用上面的核心构造器。通过核心构造器我们可以为线程池设置不同的参数,由此线程池也能表现出不同的特性。
2.4任务提交函数submit
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
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;
}
submit 方法中,参数是 Runnable 类型(也有Callable 类型),这个参数不是用于new Thread(runnable).start()中的,也就是说此处的这个参数不是用于启动线程的,这里指的是任务,任务要做的事情是 run()方法里面定义的或 Callable 中的 call() 方法里面定义的。
流程步骤如下
-
调用submit方法,传入Runnable或者Callable对象
-
判断传入的对象是否为null,为null则抛出异常,不为null继续流程
-
将传入的对象转换为RunnableFuture对象
-
执行execute方法,传入RunnableFuture对象
-
返回RunnableFuture对象
2.5 任务提交函数execute
//核心执行方法
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get();
//线程数若小于corePoolSize则新建核心工作者
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) return;
c = ctl.get();
}
//否则将任务放到任务队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//若不是running状态则将该任务从队列中移除
if (!isRunning(recheck) && remove(command)) {
//成功移除后再执行拒绝策略
reject(command);
//若线程数为0则新增一个非核心线程
}else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
}
//若队列已满则新增非核心工作者
}else if (!addWorker(command, false)) {
//若新建非核心线程失败则执行拒绝策略
reject(command);
}
}
execute方法是线程池接收任务的入口方法,当创建好一个线程池之后,我们会调用execute方法并传入一个Runnable交给线程池去执行。从上面代码中可以看到execute方法首先会去判断当前线程数是否小于corePoolSize,如果小于则调用addWorker方法新建一个核心线程去处理该任务,否则调用workQueue的offer方法将该任务放入到任务队列中。通过offer方法添加并不会阻塞线程,如果添加成功会返回true,若队列已满则返回false。在成功将任务放入到任务队列后,还会再次检查线程池是否是Running状态,如果不是则将刚刚添加的任务从队列中移除,然后再执行拒绝策略。如果从队列中移除任务失败,则再检查一下线程数是否为0(有可能刚好全部线程都被终止了),是的话就新建一个非核心线程去处理。如果任务队列已经满了,此时offer方法会返回false,接下来会再次调用addWorker方法新增一个非核心线程来处理该任务。如果期间创建线程失败,则最后会执行拒绝策略。
具体就是
-
调用execute方法,传入Runable对象
-
判断传入的对象是否为null,为null则抛出异常,不为null继续流程
-
获取当前线程池的状态和线程个数变量
-
判断当前线程数是否小于核心线程数,是走流程5,否则走流程6
-
添加线程数,添加成功则结束,失败则重新获取当前线程池的状态和线程个数变量,
-
判断线程池是否处于RUNNING状态,是则添加任务到阻塞队列,否则走流程10,添加任务成功则继续流程7
-
重新获取当前线程池的状态和线程个数变量
-
重新检查线程池状态,不是运行状态则移除之前添加的任务,有一个false走流程9,都为true则走流程11
-
检查线程池线程数量是否为0,否则结束流程,是调用addWorker(null, false),然后结束
-
调用!addWorker(command, false),为true走流程11,false则结束
-
调用拒绝策略reject(command),结束
submit和execute这两个方法的区别是:
1、execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
2、sumbit()方法用于提交需要返回值的任务,线程池会反返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且通过Future的get()方法获取到返回值,get方法会阻塞当前的线程直到任务完成。而使用get(long timeout,TimeUint unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完成。实际上submit里面执行的还是execute方法。
2.6工作线程的实现
ThreadPoolExecutor内部实现了一个Worker类,用它来表示工作线程。每个Worker对象都持有一个关联线程和分配给它的初始任务。
Worker类继承自AQS并实现了自己的加锁解锁方法,说明每个Worker对象也是一个锁对象。同时Worker类还实现了Runnable接口,因此每个Worker对象都是可以运行的。Worker类有一个唯一的构造器,需要传入一个初始任务给它,在构造器里面首先将同步状态设置为-1,这个操作主要是抑制中断直到runWorker方法运行,为啥要这样做?我们继续看下去,可以看到在设置完初始任务之后,马上就开始设置关联线程,关联线程是通过线程工厂的newThread方法来生成的,这时将Worker对象本身当作任务传给关联线程。因此在启动关联线程时(调用start方法),会运行Worker对象自身的run方法。而run方法里面紧接着调用runWorker方法,也就是说只有在runWorker方法运行时才表明关联线程已启动,这时去中断关联线程才有意义,因此前面要通过设置同步状态为-1来抑制中断。那么为啥将同步状态设置为-1就可以抑制中断?每个Worker对象都是通过调用interruptIfStarted方法来中断关联线程的,在interruptIfStarted方法内部会判断只有同步状态>=0时才会中断关联线程。因此将同步状态设置为-1能起到抑制中断的作用。
//工作者类
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
//关联线程
final Thread thread;
//初始任务
Runnable firstTask;
//完成任务数
volatile long completedTasks;
//构造器
Worker(Runnable firstTask) {
//抑制中断直到runWorker
setState(-1);
//设置初始任务
this.firstTask = firstTask;
//设置关联线程
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
//判断是否占有锁, 0代表未占用, 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;
//将活动线程和闲置线程都中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
//ignore
}
}
}
}
2.6.1、添加工作线程方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//只有以下两种情况会继续添加线程
//1.状态为running
//2.状态为shutdown,首要任务为空,但任务队列中还有任务
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {
return false;
}
for (;;) {
int wc = workerCountOf(c);
//以下三种情况不继续添加线程:
//1.线程数大于线程池总容量
//2.当前线程为核心线程,且核心线程数达到corePoolSize
//3.当前线程非核心线程,且总线程达到maximumPoolSize
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) {
return false;
}
//否则继续添加线程, 先将线程数加一
if (compareAndIncrementWorkerCount(c)) {
//执行成功则跳过外循环
break retry;
}
//CAS操作失败再次检查线程池状态
c = ctl.get();
//如果线程池状态改变则继续执行外循环
if (runStateOf(c) != rs) {
continue retry;
}
//否则表明CAS操作失败是workerCount改变, 继续执行内循环
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
int c = ctl.get();
int rs = runStateOf(c);
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
//如果线程已经开启则抛出异常
if (t.isAlive()) 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;
}
上面我们知道在execute方法里面会调用addWorker方法来添加工作线程。通过代码可以看到进入addWorker方法里面会有两层自旋循环,在外层循环中获取线程池当前的状态,如果线程池状态不符合就直接return,在内层循环中获取线程数,如果线程数超过限定值也直接return。只有经过这两重判断之后才会使用CAS方式来将线程数加1。成功将线程数加1之后就跳出外层循环去执行后面的逻辑,否则就根据不同条件来进行自旋,如果是线程池状态改变就执行外层循环,如果是线程数改变就执行内层循环。当线程数成功加1之后,后面就是去新建一个Worker对象,并在构造时传入初始任务给它。然后将这个Worker对象添加到工作者集合当中,添加成功后就调用start方法来启动关联线程。
2.6.2 工作线程的执行
//工作线程
final void runWorker(Worker w) {
//获取当前工作线程
Thread wt = Thread.currentThread();
//获取工作者的初始任务
Runnable task = w.firstTask;
//将工作者的初始任务置空
w.firstTask = null;
//将同步状态从-1设为0
w.unlock();
boolean completedAbruptly = true;
try {
//初始任务不为空则执行初始任务, 否则从队列获取任务
while (task != null || (task = getTask()) != null) {
//确保获取到任务后才加锁
w.lock();
//若状态大于等于stop, 保证当前线程被中断
//若状态小于stop, 保证当前线程未被中断
//在清理中断状态时可能有其他线程在修改, 所以会再检查一次
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);
}
}
上面我们知道,将Worker对象添加到workers集合之后就会去调用关联线程的start方法,由于传给关联线程的Runnable就是Worker对象本身,因此会调用Worker对象实现的run方法,最后会调用到runWorker方法。我们看到上面代码,进入到runWorker方法里面首先获取了Worker对象的初始任务,然后调用unlock方法将同步状态加1,由于在构造Worker对象时将同步状态置为-1了,所以这里同步状态变回0,因此在这之后才可以调用interruptIfStarted方法来中断关联线程。如果初始任务不为空就先去执行初始任务,否则就调用getTask方法去任务队列中获取任务,可以看到这里是一个while循环,也就是说工作线程在执行完自己的任务之后会不断的从任务队列中获取任务,直到getTask方法返回null,然后工作线程退出while循环最后执行processWorkerExit方法来移除自己。如果需要在所有任务执行之前或之后做些处理,可以分别实现beforeExecute方法和afterExecute方法。
2.7. 任务的获取
//从任务队列中获取任务
private Runnable getTask() {
//上一次获取任务是否超时
boolean timedOut = false;
retry:
//在for循环里自旋
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//以下两种情况会将工作者数减为0并返回null,并直接使该线程终止:
//1.状态为shutdown并且任务队列为空
//2.状态为stop, tidying或terminated
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
boolean timed;
//判断是否要剔除当前线程
for (;;) {
int wc = workerCountOf(c);
//以下两种情况会在限定时间获取任务:
//1.允许核心线程超时
//2.线程数大于corePoolSize
timed = allowCoreThreadTimeOut || wc > corePoolSize;
//以下两种情况不执行剔除操作:
//1.上次任务获取未超时
//2.上次任务获取超时, 但没要求在限定时间获取
if (wc <= maximumPoolSize && !(timedOut && timed)) {
break;
}
//若上次任务获取超时, 且规定在限定时间获取, 则将线程数减一
if (compareAndDecrementWorkerCount(c)) {
//CAS操作成功后直接返回null
return null;
}
//CAS操作失败后再次检查状态
c = ctl.get();
//若状态改变就从外层循环重试
if (runStateOf(c) != rs) {
continue retry;
}
//否则表明是workerCount改变, 继续在内循环重试
}
try {
//若timed为true, 则在规定时间内返回
//若timed为false, 则阻塞直到获取成功
//注意:闲置线程会一直在这阻塞
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
//获取任务不为空则返回该任务
if (r != null) {
return r;
}
//否则将超时标志设为true
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
工作线程在while循环里不断的通过getTask方法来从任务队列中获取任务,我们看一下getTask方法是怎样获取任务的。进入第一个for循环之后有一个if判断,从这里我们可以看到,如果线程池状态为shutdown,会继续消费任务队列里面的任务;如果线程池状态为stop,则停止消费任务队列里剩余的任务。进入第二个for循环后会给timed变量赋值,由于allowCoreThreadTimeOut变量默认是false,所以timed的值取决于线程数是否大于corePoolSize,小于为false,大于则为true。从任务队列里面获取任务的操作在try块里面,如果timed为true,则调用poll方法进行定时获取;如果timed为flase,则调用take方法进行阻塞获取。也就是说默认情况下,如果线程数小于corePoolSize,则调用take方法进行阻塞获取,即使任务队列为空,工作线程也会一直等待;如果线程数大于corePoolSize,则调用poll方法进行定时获取,在keepAliveTime时间内获取不到任务则会返回null,对应的工作线程也会被移除,但线程数会保持在corePoolSize上。当然如果设置allowCoreThreadTimeOut为true,则会一直通过调用poll方法来从任务队列中获取任务,如果任务队列长时间为空,则工作线程会减少至0。
2.8、 工作线程的退出
//删除工作线程
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//若非正常完成则将线程数减为0
if (completedAbruptly) {
decrementWorkerCount();
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//统计完成的任务总数
completedTaskCount += w.completedTasks;
//在这将工作线程移除
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
//再次检查线程池状态
int c = ctl.get();
//若状态为running或shutdown, 则将线程数恢复到最小值
if (runStateLessThan(c, STOP)) {
//线程正常完成任务被移除
if (!completedAbruptly) {
//允许核心线程超时最小值为0, 否则最小值为核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果任务队列还有任务, 则保证至少有一个线程
if (min == 0 && !workQueue.isEmpty()) {
min = 1;
}
//若线程数大于最小值则不新增了
if (workerCountOf(c) >= min) {
return;
}
}
//新增工作线程
addWorker(null, false);
}
}
工作线程如果从getTask方法中获得null,则会退出while循环并随后执行processWorkerExit方法,该方法会在这个工作线程终止之前执行一些操作,我们看到它会去统计该工作者完成的任务数,然后将其从workers集合中删除,每删除一个工作者之后都会去调用tryTerminate方法尝试终止线程池,但并不一定会真的终止线程池。从tryTerminate方法返回后再次去检查一遍线程池的状态,如果线程池状态为running或者shutdown,并且线程数小于最小值,则恢复一个工作者。这个最小值是怎样计算出来的呢?我们来看看。如果allowCoreThreadTimeOut为true则最小值为0,否则最小值为corePoolSize。但还有一个例外情况,就是虽然允许核心线程超时了,但是如果任务队列不为空的话,那么必须保证有一个线程存在,因此这时最小值设为1。后面就是判断如果工作线程数大于最小值就不新增线程了,否则就新增一个非核心线程。从这个方法可以看到,每个线程退出时都会去判断要不要再恢复一个线程,因此线程池中的线程总数也是动态增减的。
2.9. 线程池的终止
//平缓关闭线程池
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查是否有关闭的权限
checkShutdownAccess();
//将线程池状态设为shutdown
advanceRunState(SHUTDOWN);
//中断闲置的线程
interruptIdleWorkers();
//对外提供的钩子
onShutdown();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
//立刻关闭线程池
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;
}
可以通过两个方法来终止线程池,通过调用shutdown方法可以平缓的终止线程池,通过调用shutdownNow方法可以立即终止线程池。调用shutdown()方法后首先会将线程池状态设置为shutdown,这时线程池会拒绝接收外部传过来的任务,然后调用interruptIdleWorkers()方法中断闲置线程,剩余的线程会继续消费完任务队列里的任务之后才会终止。调用shutdownNow()方法会将线程池状态设置为stop,这是线程池也不再接收外界的任务,并且马上调用interruptWorkers()方法将所有工作线程都中断了,然后排干任务队列里面没有被处理的任务,最后返回未被处理的任务集合。调用shutdown()和shutdownNow()方法后还未真正终止线程池,这两个方法最后都会调用tryTerminate()方法来终止线程池。
2.9.1尝试终止线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//以下两种情况终止线程池,其他情况直接返回:
//1.状态为stop
//2.状态为shutdown且任务队列为空
if (isRunning(c) || runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) {
return;
}
//若线程不为空则中断一个闲置线程后直接返回
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//将状态设置为tidying
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//线程池终止后做的事情
terminated();
} finally {
//将状态设置为终止状态(TERMINATED)
ctl.set(ctlOf(TERMINATED, 0));
//唤醒条件队列所有线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
//若状态更改失败则再重试
}
}
tryTerminate()方法在其他很多地方也被调用过,比如processWorkerExit()和addWorkerFailed()。调用该方法来尝试终止线程池,在进入for循环后第一个if判断过滤了不符合条件的终止操作,只有状态为stop,或者状态为shutdown且任务队列为空这两种情况才能继续执行。第二个if语句判断工作线程数量是否为0,不为0的话也直接返回。经过这两重判断之后才符合终止线程池的条件,于是先通过CAS操作将线程池状态设置为tidying状态,在tidying状态会调用用户自己实现的terminated()方法来做一些处理。到了这一步,不管terminated()方法是否成功执行最后都会将线程池状态设置为terminated,也就标志着线程池真正意义上的终止了。最后会唤醒所有等待线程池终止的线程,让它们继续执行。
2.10、 ThreadFactory
ThreadFactory是一个接口,其中只有一个方法,即newThread(Runnable r)。从这个方法名字就可以知道,这接口是用来创建新的线程的。其使用也很简单,仅仅只需要实现newThread方法,根据自己的需要进行线程的创建即可。采用这种工厂方法的方式创建线程的优势在于,用户可以根据自己的需要进行线程的创建,同时用户也可以自定义线程工厂,比如保存线程创建线程的数量的ThreadFactory,创建特定优先级的线程的TrheadFacotry等等。
public interface ThreadFactory {
Thread newThread(Runnable r);
}
Executors.defaultThreadFactory()实现
public static ThreadFactory defaultThreadFactory() {
return
new DefaultThreadFactory();
}
在JDK中,实现ThreadFactory就只有一个地方。而更多的时候,我们都是继承它然后自己来写这个线程工厂的。下面的代码中在类Executors当中。默认的我们创建线程池时使用的就是这个线程工厂。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);//原子类,线程池编号
private final ThreadGroup group;//线程组
private final AtomicInteger threadNumber = new AtomicInteger(1);//线程数目
private final String namePrefix;//为每个创建的线程添加的前缀
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();//取得线程组
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
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;
}
}
在上面的代码中,可以看到线程池中默认的线程工厂实现是很简单的,它做的事就是统一给线程池中的线程设置线程group、统一的线程前缀名。以及统一的优先级。
2.11、拒绝策略(饱和策略)
用于如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolExecutor中有四种策略:
ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理,该异常由调用者来捕获。当不指定RejectedExecutionHandler饱和策略的话来配置线程池的时候默认使用的是就是这个策略,这代表将丢失对这个任务的处理。
ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,做到既不抛弃任务,也不抛出异常。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,当线程池被填满的时候,此策略为我们提供可伸缩队列。如果应用程序可以承受此延迟并且不能任务丢弃任何一个任务请求的话,可以选择这个策略。 CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。 缺点是可能会阻塞主线程。
ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。
除了上述四种,当然还可以自定义饱和策略。
3、总结
线程池可以在进行多线程编码上提供极大便利,就好像数据库连接池一样,减少了线程的开销,提供了线程的复用。而且ThreadPoolExecutor也提供了一些未实现的方法,供我们来使用,像beforeExecute、afterExecute等方法,我们可以通过这些方法来对线程进行进一步的管理和统计。
在使用线程池上好需要注意,提交的线程任务可以分为CPU密集型任务和IO密集型任务,然后根据任务的不同进行分配不同的线程数量。
CPU密集型任务:应当分配较少的线程,比如CPU个数相当的大下
IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
混合型任务:可以将其拆分为 CPU 密集型任务以及 IO 密集型任务,这样来分别配置。