带你拿下线程池——ThreadPoolExecutor
Java中的线程池一般都是围绕着ThreadPoolExecutor来展开的。其他的实现基本上都是基于它或者模仿它来实现的,因此掌握了ThreadPoolExecutor,也就掌握了线程池的精髓。
本文首先讲述ThreadPoolExecutor的用法,接着会分析ThreadPoolExecutor的源码,接着会讲解我们该如何配置线程池的参数,最后讲解一下我们项目中的一些使用经验。
一、使用线程池
Java线程池无论是在Java项目还是Web项目中几乎都会用到。那我们为什么要用线程池呢?
我们在创建线程的时候要么继承Thread类,要么实现Runnable接口。但这些方式会使的线程不受风险控制,频繁创建和销毁开销很大,并且也不好管理。而Java使用线程池来启动线程会使得比使用Thread.start方法更好。线程池的作用简单的说就是把线程统一管理。与直接启动线程的方式相比,线程池具有如下优势:
- 降低系统资源消耗,通过重用已经存在的线程,降低线程创建和销毁造成的消耗。
- 提高系统响应速度,当有任务到达时,通过复用已经存在的线程,无需等待线程的创建便能立即执行。
- 方便线程并发数管理。如果线程无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成CPU过度切换。
- 可以提供更强大的功能,如延时定时线程池。
JDK中提供了一个工场类Executors,使用这个类可以很方便的创建线程池。常用的方法有:
Executors.newSingleThreadExecutor();
这个方法会创建一个单线程的线程池,这个线程池只有一个线程工作。也就相当于单线程串行执行所有任务。如果这个工作线程因为异常结束,那么会创建一个新线程来代替它。此线程保证所有任务的执行顺序按照任务的提交顺序。
Executors.newFixedThreadPool(5);
创建一个固定大小的线程池。没次提交任务就创建一个线程池,直到线程达到线程池的最大值。线程池一旦达到最大值就保持不变,如果某个工作线程因为异常结束,那么会创建一个新线程来代替它。
Executors.newCachedThreadPool();
创建一个可缓存的线程池,如果线程池大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程(60秒不执行任务);当任务增加时,此线程池又可以智能的添加新线程来执行任务。此线程不会对线程池大小做限制,线程池大小完全依赖操作系统能够创建的最大线程大小。
Executors.newScheduledThreadPool(5);
创建一个大小无线的线程池。此线程池支持定时已经周期执行任务的需求。
除了使用Executors工厂类来创建线程池,我们还可以直接创建ThreadPoolExecutor对象来定制创建线程池。
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(5,
10, 1L, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Self-Thread");
}
});
//提交一个Runnable对象来执行线程
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//通过提交一个Callable对象来获取到执行完任务后的结果。
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(10000);
return "返回结果";
}
});
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程池
executorService.shutdown();
}
}
ThreadPoolExecutor的构造函数包含一下参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数的含义如下,这些参数非常重要!非常重要!
-
corePoolSize:线程池核心线程的数量。
-
maximumPoolSize: 线程池可创建的最大线程数量;
-
keepAliveTime: 当线程数量超过了corePoolSize指定的线程数,并且空闲线程空闲的时间达到当前参数指定的时间时该线程就会被销毁,如果调用过allowCoreThreadTimeOut(boolean value)方法允许核心线程过期,那么该策略针对核心线程也是生效的;
-
unit: 指定了keepAliveTime的单位,可以为毫秒,秒,分,小时等
-
workQueue: 存储未执行的任务的队列;
-
threadFactory: 创建线程的工厂,如果未指定则使用默认的线程工厂;
-
handler: 指定了当任务队列已满,并且没有可用线程执行任务时对新添加的任务的处理策略;
ThreadPoolExecutor中也包含许多的执行任务的方法,下面我就挑几个常用的说明一下:
//执行一个任务,没有返回结果
public void execute(Runnable command);
//执行一个任务,成功后返回一个Future实例,可以获取到执行结果
public <T> Future<T> submit(Callable<T> task);
//执行一个任务,成功后返回一个Future实例,可以获取到执行结果为入参的result
public <T> Future<T> submit(Runnable task, T result);
//执行一个任务,成功后返回一个Future实例
public Future<?> submit(Runnable task);
//等待任务执行完关闭线程池
public void shutdown();
//不等任务执行完,立刻关闭线程池,返回未被执行的任务
public List<Runnable> shutdownNow();
二、ThreadPoolExecutor的源码
2.1 Java线程池的类关系
概括一下:
- Executor是最基础的执行接口,只定义了一个executor方法;
- ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,可以说是真正的线程池接口;
- AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法;
- TheadPoolExecutor继承了AbstractExecutorService,是线程池的具体实现;
- ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"功能ExecutorService;
- ScheduledThreadPoolExecutor既继承了TheadPoolExecutor线程池,也实现了ScheduledExecutorService接口,是带"周期执行"功能的线程池;
- Executors是线程池的静态工厂,其提供了快捷创建线程池的静态方法。
2.2 Executor接口
Executor就是一个执行器接口,执行提交的 Runnable 任务的对象。该接口提供了一种将任务提交与每个任务将如何运行的机制分离的方法,包括线程使用、调度等的细节。
public interface Executor {
//在将来的某个时间执行给定的命令。 根据 Executor 实现的判断,该命令可以在新线程、池线程或调用线程中执行。
void execute(Runnable command);
}
Doug Lea教授给的例子可以帮助我们理解这个接口的定义意图。
class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
final Executor executor;
Runnable active;
SerialExecutor(Executor executor) {
this.executor = executor;
}
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.execute(active);
}
}
}
2.3 ExecutorService接口
一般我们定义一个线程池的时候,往往都是使用这个接口,因为这个接口中定义的一系列方法大部分情况下已经可以满足我们的需要了。
public interface ExecutorService extends Executor {
// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
void shutdown();
// 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
// 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
List<Runnable> shutdownNow();
// 线程池是否已关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
// 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
boolean isTerminated();
// 等待所有任务完成,并设置超时时间
// 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
// 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一个 Callable 任务
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
// 因为 Runnable 的 run 方法本身并不返回任何东西
<T> Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
// 执行所有任务,返回 Future 类型的一个 list
<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;
}
这些方法都很好理解,一个简单的线程池主要就是这些功能,能提交任务,能获取结果,能关闭线程池,这也是为什么我们经常用这个接口的原因。
2.3 FutureTask类
再继续往下讲之前,我们先来看一下一个相关的类FutureTask类,这个类的继承关系如图所示
FutureTask类通过实现RunnableFuture接口,间接实现的Future接口和Runnable接口。线程池执行的任务都会被包装成FutureTask,然后再通过execute方法将其交给线程池来执行。Runnable 的 void run() 方法是没有返回值的,所以,通常,如果我们需要的话,会在 submit 中指定第二个参数作为返回值:
<T> Future<T> submit(Runnable task, T result);
需要了解的可以看一下我对FutureTask类的分析。
2.4 AbstractExecutorService
AbstractExecutorService 抽象类派生自 ExecutorService 接口,然后在其基础上实现了几个实用的方法,这些方法提供给子类进行调用。这个抽象类实现了 invokeAny 方法和 invokeAll 方法,这里的两个 newTaskFor 方法也比较有用,用于将任务包装成 FutureTask。
public abstract class AbstractExecutorService implements ExecutorService {
//下面的这两个方法,将提交的任务封装成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);
}
//submit方法提交任务,会将任务封装成FutureTask, 最后也是通过调用execute方法执行
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;
}
//此方法目的:将 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();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
// ExecutorCompletionService 不是一个真正的执行器,参数 this 才是真正的执行器
// 它对执行器进行了包装,每个任务结束后,将结果保存到内部的一个 completionQueue 队列中
// 这也是为什么这个类的名字里面有个 Completion 的原因吧。
ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);
try {
// Record exceptions so that if we fail to obtain any
// result, we can throw the last exception we got.
// 用于保存异常信息,此方法如果没有得到任何有效的结果,那么我们可以抛出最后得到的一个异常
ExecutionException ee = null;
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
// Start one task for sure; the rest incrementally
// 首先先提交一个任务,后面的任务到下面的 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() 返回了
else if (active == 0)
break;
// 这里也是 else if。这里说的是,没有任务了,但是设置了超时时间,这里检测是否超时
else if (timed) {
// 带等待的 poll 方法
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
// 这里是 else。说明,没有任务需要提交,但是池中的任务没有完成,还没有超时(如果设置了超时)
// take() 方法会阻塞,直到有元素返回,说明有任务结束了
else
f = ecs.take();
}
if (f != null) {
--active;
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
/*
* 我感觉上面这一段并不是很好理解,这里简单说下。
* 1. 首先,这在一个 for 循环中,我们设想每一个任务都没那么快结束,
* 那么,每一次都会进到第一个分支,进行提交任务,直到将所有的任务都提交了
* 2. 任务都提交完成后,如果设置了超时,那么 for 循环其实进入了“一直检测是否超时”
这件事情上
* 3. 如果没有设置超时机制,那么不必要检测超时,那就会阻塞在 ecs.take() 方法上,
等待获取第一个执行结果
* ?. 这里我还没理解 active == 0 这个分支的到底是干嘛的?
*/
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);
}
}
}
到这里,我们发现,这个抽象类包装了一些基本的方法,可是像 submit、invokeAny、invokeAll 等方法,它们都没有真正开启线程来执行任务,它们都只是在方法内部调用了 execute 方法,所以最重要的 execute(Runnable runnable) 方法还没出现,需要等具体执行器来实现这个最重要的部分,这里我们要说的就是 ThreadPoolExecutor 类了。
2.5 ThreadPoolExecutor
ThreadPoolExecutor的属性
ThreadPoolExecutor 是 JDK 中的线程池实现,这个类实现了一个线程池需要的各个方法,它实现了任务提交、线程管理、监控等等方法。我们可以基于它来进行业务上的扩展,以实现我们需要的其他功能,比如实现定时任务的类 ScheduledThreadPoolExecutor 就继承自 ThreadPoolExecutor。当然,这不是本文关注的重点,下面,还是赶紧进行源码分析吧。先看一下定义的属性:
// 线程池的控制状态,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//值为29,用来表示偏移量
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的最大容量,其值的二进制为:00011111111111111111111111111111(29个1)
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的运行状态,总共有5个状态,用高3位来表示
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;
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//全局锁,对线程池状态等属性修改时需要使用这个锁
private final ReentrantLock mainLock = new ReentrantLock();
//线程池中工作线程的集合,访问和修改需要持有全局锁
private final HashSet<Worker> workers = new HashSet<Worker>();
// 终止条件
private final Condition termination = mainLock.newCondition();
//线程池中曾经出现过的最大线程数
private int largestPoolSize;
//已完成任务的数量
private long completedTaskCount;
//线程工厂
private volatile ThreadFactory threadFactory;
//任务拒绝策略
private volatile RejectedExecutionHandler handler;
//线程存活时间
private volatile long keepAliveTime;
//是否允许核心线程超时
private volatile boolean allowCoreThreadTimeOut;
//核心池大小,若allowCoreThreadTimeOut被设置,核心线程全部空闲超时被回收的情况下会为0
private volatile int corePoolSize;
//最大池大小,不得超过CAPACITY
private volatile int maximumPoolSize;
//默认的任务拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
private static final RuntimePermission shutdownPerm =
new RuntimePermission("modifyThread");
private final AccessControlContext acc;
ThreadPoolExecutor线程池有5个状态,分别是:
- RUNNING:可以接受新的任务,也可以处理阻塞队列里的任务
- SHUTDOWN:不接受新的任务,但是可以处理阻塞队列里的任务
- STOP:不接受新的任务,不处理阻塞队列里的任务,中断正在处理的任务
- TIDYING:过渡状态,也就是说所有的任务都执行完了,当前线程池已经没有有效的线程,这个时候线程池的状态将会TIDYING,并且将要调用terminated方法
- TERMINATED:终止状态。terminated方法调用完成以后的状态
状态和线程数在ThreadPoolExecutor内部使用一个整型变量保存,没错,一个变量表示两种含义。在ThreadPoolExecutor,整型中32位的前3位用来表示线程池状态,后3位表示线程池中有效的线程数。
// 初始化状态和数量,状态为RUNNING,线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
使用ThreadPoolExecutor执行任务的时候,可以使用execute或submit方法,submit方法如下:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
很明显地看到,submit方法内部使用了execute方法,而且submit方法是有返回值的。在调用execute方法之前,使用FutureTask包装一个Runnable,这个FutureTask就是返回值。由于submit方法内部调用execute方法,所以execute方法就是执行任务的方法,来看一下execute方法,execute方法内部分3个步骤进行处理。如下图所示:
下面着重看一下execute()方法的实现。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取线程池控制状态
int c = ctl.get();
// (1)
//worker数量小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
//创建worker,addWorker方法boolean参数用来判断是否创建核心线程
if (addWorker(command, true))
//成功则返回
return;
//失败则再次获取线程池控制状态
c = ctl.get();
}
//(2)
//线程池处于RUNNING状态,将任务加入workQueue任务缓存队列
if (isRunning(c) && workQueue.offer(command)) {
// 再次检查,获取线程池控制状态,防止在任务入队的过程中线程池关闭了或者线程池中没有线程了
int recheck = ctl.get();
//线程池不处于RUNNING状态,且将任务从workQueue移除成功
if (! isRunning(recheck) && remove(command))
//采取任务拒绝策略
reject(command);
//worker数量等于0
else if (workerCountOf(recheck) == 0)
//创建worker
addWorker(null, false);
}
//(3)
else if (!addWorker(command, false)) //创建worker
reject(command); //如果创建worker失败,采取任务拒绝策略
}
execute()方法的执行流程可以总结如下:
- 若线程池工作线程数量小于corePoolSize,则创建新线程来执行任务
- 若工作线程数量大于或等于corePoolSize,则将任务加入BlockingQueue
- 若无法将任务加入BlockingQueue(BlockingQueue已满),且工作线程数量小于maximumPoolSize,则创建新的线程来执行任务
- 若工作线程数量达到maximumPoolSize,则创建线程失败,采取任务拒绝策略
可以结合上面的图片来理解。
创建线程
从execute()方法的实现可以看出,addWorker()方法主要负责创建新的线程并执行任务,代码实现如下:
//addWorker有两个参数:Runnable类型的firstTask,用于指定新增的线程执行的第一个任务;boolean类型的core,表示是否创建核心线程
//该方法的返回值代表是否成功新增一个线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// (1)
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;
//CAS操作递增workCount
//如果成功,那么创建线程前的所有条件校验都满足了,准备创建线程执行任务,退出retry循环
//如果失败,说明有其他线程也在尝试往线程池中创建线程(往线程池提交任务可以是并发的),则继续往下执行
if (compareAndIncrementWorkerCount(c))
break retry;
//重新获取线程池控制状态
c = ctl.get();
// 如果线程池的状态发生了变更,如有其他线程关闭了这个线程池,那么需要回到外层的for循环
if (runStateOf(c) != rs)
continue retry;
//如果只是CAS操作失败的话,进入内层的for循环就可以了
}
}
//到这里,创建线程前的所有条件校验都满足了,可以开始创建线程来执行任务
//worker是否已经启动
boolean workerStarted = false;
//是否已将这个worker添加到workers这个HashSet中
boolean workerAdded = false;
Worker w = null;
try {
//创建一个worker,从这里可以看出对线程的包装
w = new Worker(firstTask);
//取出worker中的线程对象,Worker的构造方法会调用ThreadFactory来创建一个新的线程
final Thread t = w.thread;
if (t != null) {
//获取全局锁, 并发的访问线程池workers对象必须加锁,持有锁的期间线程池也不会被关闭
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//重新获取线程池的运行状态
int rs = runStateOf(ctl.get());
//小于SHUTTDOWN即RUNNING
//等于SHUTDOWN并且firstTask为null,不接受新的任务,但是会继续执行等待队列中的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//worker里面的thread不能是已启动的
if (t.isAlive())
throw new IllegalThreadStateException();
//将新创建的线程加入到线程池中
workers.add(w);
int s = workers.size();
// 更新largestPoolSize
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//线程添加线程池成功,则启动新创建的线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//若线程启动失败,做一些清理工作,例如从workers中移除新添加的worker并递减wokerCount
if (! workerStarted)
addWorkerFailed(w);
}
//返回线程是否启动成功
return workerStarted;
}
因为代码(1)处的逻辑不利于理解,我们通过(1)的等价实现来理解:
if (rs>=SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
//等价实现
rs>=SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
其含义为,满足下列条件之一则直接返回false,线程创建失败:
- rs > SHUTDOWN,也就是STOP,TIDYING或TERMINATED,此时不再接受新的任务,且中断正在执行的任务
- rs = SHUTDOWN且firstTask != null,此时不再接受任务,但是仍会处理任务缓存队列中的任务
- rs = SHUTDOWN,队列为空
多说一句,若线程池处于 SHUTDOWN, firstTask 为 null,且 workQueue 非空,那么还得创建线程继续处理任务缓存队列中的任务。
总结一下,addWorker()方法完成了如下几件任务:
- 原子性的增加workerCount
- 将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中
- 启动worker对应的线程
- 若线程启动失败,回滚worker的创建动作,即从workers中移除新添加的worker,并原子性的减少workerCount
从addWorker()方法的实现可以看出,工作线程的创建和启动都跟ThreadPoolExecutor中的内部类Worker有关。下面我们分析Worker类来看一下工作线程的实现。Worker类继承自AQS类,具有锁的功能;实现了Runable接口,可以将自身作为一个任务在线程中执行。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
Worker的主要字段就下面三个,代码也比较简单。
//用来封装worker的线程,线程池中真正运行的线程,通过线程工厂创建而来
final Thread thread;
//worker所对应的第一个任务,可能为空
Runnable firstTask;
//记录当前线程完成的任务数
volatile long completedTasks;
Worker的构造函数如下。
Worker(Runnable firstTask) {
//设置AQS的state为-1,在执行runWorker()方法之前阻止线程中断
setState(-1);
//初始化第一个任务
this.firstTask = firstTask;
//利用指定的线程工厂创建一个线程,注意,参数是Worker实例本身this
//也就是当执行start方法启动线程thread时,真正执行的是Worker类的run方法
this.thread = getThreadFactory().newThread(this);
}
Worker类继承了AQS类,重写了其相应的方法,实现了一个自定义的同步器,实现了不可重入锁。
//是否持有独占锁
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) {
//设置独占线程为null
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(); }
再来看一下Worker类的run()方法的实现,会发现run()方法最终调用了ThreadPoolExecutor类的runWorker()方法。
public void run() {
runWorker(this);
}
线程复用机制
通过上文可以知道,worker中的线程start 后,执行的是worker的run()方法,而run()方法最终会调用ThreadPoolExecutor类的runWorker()方法,runWorker()方法实现了线程池中的线程复用机制。下面我们来看一下runWorker()方法的实现。
final void runWorker(Worker w) {
//获取当前线程
Thread wt = Thread.currentThread();
//获取w的firstTask
Runnable task = w.firstTask;
//设置w的firstTask为null
w.firstTask = null;
// 释放锁,设置AQS的state为0,允许中断
w.unlock();
//用于标识线程是否异常终止,finally中processWorkerExit()方法会有不同逻辑
boolean completedAbruptly = true;
try {
//循环调用getTask()获取任务,不断从任务缓存队列获取任务并执行
while (task != null || (task = getTask()) != null) {
//进入循环内部,代表已经获取到可执行的任务,则对worker对象加锁,保证线程在执行任务过程中不会被中断
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) || //若线程池状态大于等于STOP,那么意味着该线程要中断
(Thread.interrupted() && //线程被中断
runStateAtLeast(ctl.get(), STOP))) && //且是因为线程池内部状态变化而被中断
!wt.isInterrupted()) //确保该线程未被中断
//发出中断请求
wt.interrupt();
try {
//开始执行任务前的Hook方法
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 {
//执行任务后的Hook方法
afterExecute(task, thrown);
}
} finally {
//置空task,准备通过getTask()获取下一个任务
task = null;
//completedTasks递增
w.completedTasks++;
//释放掉worker持有的独占锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
//到这里,线程执行结束,需要执行结束线程的一些清理工作
//线程执行结束可能有两种情况:
//1.getTask()返回null,也就是说,这个worker的使命结束了,线程执行结束
//2.任务执行过程中发生了异常
//第一种情况,getTask()返回null,那么getTask()中会将workerCount递减
//第二种情况,workerCount没有进行处理,这个递减操作会在processWorkerExit()中处理
processWorkerExit(w, completedAbruptly);
}
}
runWorker()方法是线程池的核心,实现了线程池中的线程复用机制,来看一下。runWorker()方法都做了哪些工作:
- 行第一个任务firstTask之后,循环调用getTask()方法获取任务,不断从任务缓存队列获取任务并执行;
- 获取到任务之后就对worker对象加锁,保证线程在执行任务的过程中不会被中断,任务执行完会释放锁;
- 在执行任务的前后,可以根据业务场景重写beforeExecute()和afterExecute()等Hook方法;
- 执行通过getTask()方法获取到的任务
- 线程执行结束后,调用processWorkerExit()方法执行结束线程的一些清理工作。
从runWorker()方法的实现可以看出,runWorker()方法中主要调用了getTask()方法和processWorkerExit()方法,下面分别看一下这两个方法的实现。getTask()方法用来不断地从任务缓存队列获取任务并交给线程执行,下面分析一下其实现。
private Runnable getTask() {
//标识当前线程是否超时未能获取到task对象
boolean timedOut = false;
for (;;) {
//获取线程池的控制状态
int c = ctl.get();
//获取线程池的运行状态
int rs = runStateOf(c);
//如果线程池状态大于等于STOP,或者处于SHUTDOWN状态,并且阻塞队列为空,线程池工作线程数量递减,方法返回null,回收线程
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取worker数量
int wc = workerCountOf(c);
//标识当前线程在空闲时,是否应该超时回收
// 如果allowCoreThreadTimeOut为ture,或当前线程数大于核心池大小,则需要超时回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果worker数量大于maximumPoolSize(有可能调用了 setMaximumPoolSize(),导致worker数量大于maximumPoolSize)
if ((wc > maximumPoolSize || (timed && timedOut)) //或者获取任务超时
&& (wc > 1 || workQueue.isEmpty())) { //workerCount大于1或者阻塞队列为空(在阻塞队列不为空时,需要保证至少有一个工作线程)
if (compareAndDecrementWorkerCount(c))
//线程池工作线程数量递减,方法返回null,回收线程
return null;
//线程池工作线程数量递减失败,跳过剩余部分,继续循环
continue;
}
try {
//如果允许超时回收,则调用阻塞队列的poll(),只在keepAliveTime时间内等待获取任务,一旦超过则返回null
//否则调用take(),如果队列为空,线程进入阻塞状态,无限时等待任务,直到队列中有可取任务或者响应中断信号退出
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//若task不为null,则返回成功获取的task对象
if (r != null)
return r;
// 若返回task为null,表示线程空闲时间超时,则设置timeOut为true
timedOut = true;
} catch (InterruptedException retry) {
//如果此worker发生了中断,采取的方案是重试,没有超时
//在哪些情况下会发生中断?调用setMaximumPoolSize(),shutDown(),shutDownNow()
timedOut = false;
}
}
}
接下来总结一下getTask()方法会在哪些情况下返回:
- 线程池处于RUNNING状态,阻塞队列不为空,返回成功获取的task对象
- 线程池处于SHUTDOWN状态,阻塞队列不为空,返回成功获取的task对象
- 线程池状态大于等于STOP,返回null,回收线程
- 线程池处于SHUTDOWN状态,并且阻塞队列为空,返回null,回收线程
- worker数量大于maximumPoolSize,返回null,回收线程
- 线程空闲时间超时,返回null,回收线程。
processWorkerExit()的实现
processWorkerExit()方法负责执行结束线程的一些清理工作,下面分析一下其实现。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果用户任务执行过程中发生了异常,则需要递减workerCount
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
//将worker完成任务的数量累加到总的完成任务数中
completedTaskCount += w.completedTasks;
//从workers集合中移除该worker
workers.remove(w);
} finally {
//释放锁
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
//获取线程池控制状态
int c = ctl.get();
if (runStateLessThan(c, STOP)) { //线程池运行状态小于STOP
if (!completedAbruptly) { //如果用户任务执行过程中发生了异常,则直接调用addWorker()方法创建线程
//是否允许核心线程超时
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//允许核心超时并且workQueue阻塞队列不为空,那线程池中至少有一个工作线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果工作线程数量workerCount大于等于核心池大小corePoolSize,
//或者允许核心超时并且workQueue阻塞队列不为空时,线程池中至少有一个工作线程,直接返回
if (workerCountOf(c) >= min)
return;
//若不满足上述条件,则调用addWorker()方法创建线程
}
//创建新的线程取代当前线程
addWorker(null, false);
}
}
////processWorkerExit()方法中主要调用了tryTerminate()方法,下面看一下tryTerminate()方法的实现。
final void tryTerminate() {
for (;;) {
//获取线程池控制状态
int c = ctl.get();
if (isRunning(c) || //线程池的运行状态为RUNNING
runStateAtLeast(c, TIDYING) || //线程池的运行状态大于等于TIDYING
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) //线程池的运行状态为SHUTDOWN且阻塞队列不为空
//不能终止,直接返回
return;
//只有当线程池的运行状态为STOP,或线程池运行状态为SHUTDOWN且阻塞队列为空时,可以执行到这里
//如果线程池工作线程的数量不为0
if (workerCountOf(c) != 0) {
//仅仅中断一个空闲的worker
interruptIdleWorkers(ONLY_ONE);
return;
}
//只有当线程池工作线程的数量为0时可以执行到这里
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //CAS操作设置线程池运行状态为TIDYING,工作线程数量为0
try {
//执行terminated()钩子方法
terminated();
} finally {
//设置线程池运行状态为TERMINATED,工作线程数量为0
ctl.set(ctlOf(TERMINATED, 0));
//唤醒在termination条件上等待的所有线程
termination.signalAll();
}
return;
}
} finally {
//释放锁
mainLock.unlock();
}
//若CAS操作失败则重试
}
}
tryTerminate()方法的作用是尝试终止线程池,它会在所有可能终止线程池的地方被调用,满足终止线程池的条件有两个:首先,线程池状态为STOP,或者为SHUTDOWN且任务缓存队列为空;其次,工作线程数量为0。
满足了上述两个条件之后,tryTerminate()方法获取全局锁,设置线程池运行状态为TIDYING,之后执行terminated()钩子方法,最后设置线程池状态为TERMINATED。
至此,线程池运行状态变为TERMINATED,工作线程数量为0,workers已清空,且workQueue也已清空,所有线程都执行结束,线程池的生命周期到此结束。
shutdown()的实现
shutdown()方法将线程池运行状态设置为SHUTDOWN,此时线程池不会接受新的任务,但会处理阻塞队列中的任务。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
//检查shutdown权限
checkShutdownAccess();
//设置线程池运行状态为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断所有空闲worker
interruptIdleWorkers();
//用onShutdown()钩子方法
onShutdown();
} finally {
//释放锁
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
//shutdown()方法首先会检查是否具有shutdown的权限,然后设置线程池的运行状态为SHUTDOWN,之后中断所有空闲的worker,再调用onShutdown()钩子方法,最后尝试终止线程池。
//shutdown()方法调用了interruptIdleWorkers()方法中断所有空闲的worker,其实现如下。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
//onlyOne标识是否只中断一个线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
//遍历workers集合
for (Worker w : workers) {
//worker对应的线程
Thread t = w.thread;
//线程未被中断且成功获得锁
if (!t.isInterrupted() && w.tryLock()) {
try {
//发出中断请求
t.interrupt();
} catch (SecurityException ignore) {
} finally {
//释放锁
w.unlock();
}
}
//若只中断一个线程,则跳出循环
if (onlyOne)
break;
}
} finally {
//释放锁
mainLock.unlock();
}
}
shutdownNow()的实现
shutdownNow()方法将线程池运行状态设置为STOP,此时线程池不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
//检查shutdown权限
checkShutdownAccess();
//设置线程池运行状态为STOP
advanceRunState(STOP);
//中断所有worker
interruptWorkers();
//将任务缓存队列中等待执行的任务取出并放到list中
tasks = drainQueue();
} finally {
//释放锁
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
//返回任务缓存队列中等待执行的任务列表
return tasks;
}
shutdownNow()方法与shutdown()方法相似,不同之处在于,前者设置线程池的运行状态为STOP,之后中断所有的worker(并非只是空闲的worker),尝试终止线程池之后,返回任务缓存队列中等待执行的任务列表。shutdownNow()方法调用了interruptWorkers()方法中断所有的worker(并非只是空闲的worker),其实现如下。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
//获取全局锁
mainLock.lock();
try {
//遍历workers集合
for (Worker w : workers)
//调用Worker类的interruptIfStarted()方法中断线程
w.interruptIfStarted();
} finally {
//释放锁
mainLock.unlock();
}
}
三、设置线程池的大小
CPU密集型任务
cpu密集型任务也叫做计算密集型任务,在拥有N个处理器的系统上,当现成池的大小为N+1时,通常能实现最优的利用率。
IO密集型任务
对于包括IO操作或其他阻塞性操作的任务,由于现成并不会一直执行,因此现成池的规模应该更大。要正确设置线程池的大小,你必须估算出任务的等待时间和执行时间的比值。这种估算不需要很精确,然后可以通过下面的公式计算线程数:
$$
Nthreads=NcpuUcpu(1+w/c)
$$
其中 Ncpu=CPU核心数 Ucpu=cpu使用率,0~1 W/C=等待时间与计算时间的比率。假设 CPU 100%运转,即 线程数=Ncpu(1+w/c)。:一般情况存在IO,那么肯定w/c>1,保守点取1即Nthreads=Ncpu(1+1)=2Ncpu。
四、总结
至此,我们已经阅读了线程池框架的核心类ThreadPoolExecutor类的大部分源码,由衷地赞叹这个类很多地方设计的巧妙之处:
- 将线程池的运行状态和工作线程数量打包在一起,并使用了大量的位运算
- 使用CAS操作更新线程控制状态ctl,确保对ctl的更新是原子操作
- 内部类Worker类继承了AQS,实现了一个自定义的同步器,实现了不可重入锁
- 使用while循环自旋地从任务缓存队列中获取任务并执行,实现了线程复用机制
- 调用interrupt()方法中断线程,但注意该方法并不能直接中断线程的运行,只是发出了中断信号,配合BlockingQueue的take(),poll()方法的使用,打断线程的阻塞状态。
其实,线程池的本质就是生产者消费者模式,线程池的调用者不断向线程池提交任务,线程池里面的工作线程不断获取这些任务并执行(从任务缓存队列获取任务或者直接执行任务)。