概要
Submit方法步骤:
首先用RunnableFuture对任务封装-》执行ThreadPoolExecutor的executes方法-》判断当前执行线程是否小于核心线程数(线程总数有记录,运行的线程都封装到worker中添加到hashset中)-》若小于则创建线程-》封装在实现了Runnable类的worker中(work中创建线程,worker作为runnable传入)-》执行worker中线程的start方法-》执行worker的run方法-》在run方法中调传进来的任务run方法-》若是大于核心线程数则放入阻塞队列中去-》若核心线程都在跑并且阻塞队列也满了,判断是否大于最大线程数-》若小于最大线程数-》则创建临时线程进行跑-》若大于最大线程数,则用拒绝策略进行拒绝
为什么需要使用线程池
在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
1、如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
2、提高线程的可管理性。线程总数可预知,避免用户主动创建无限多线程导致死机风险,还可以进行线程统一的分配、调优和监控。
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。
以下是本文的目录大纲:
一.Java中的ThreadPoolExecutor类
二.深入剖析线程池实现原理
三.使用示例
四.如何合理配置线程池的大小
一.Java中的ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class ThreadPoolExecutorextends AbstractExecutorService { ..... public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler); ... } |
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
- threadFactory:线程工厂,主要用来创建线程;
- handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
具体参数的配置与线程池的关系将在下一节讲述。
从上面给出的ThreadPoolExecutor类的代码可以知道,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public abstract class AbstractExecutorServiceimplements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { }; protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { }; public Future<?> submit(Runnable task) {}; public <T> Future<T> submit(Runnable task, T result) { }; public <T> Future<T> submit(Callable<T> task) { }; private <T> T doInvokeAny(Collection<?extends Callable<T>> tasks, boolean timed,long nanos) throws InterruptedException, ExecutionException, TimeoutException { }; public <T> T invokeAny(Collection<?extends Callable<T>> tasks) throws InterruptedException, ExecutionException { }; public <T> T invokeAny(Collection<?extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { }; public <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks) throws InterruptedException { }; public <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { }; } |
AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。
我们接着看ExecutorService接口的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public interface ExecutorServiceextends Executor {
void shutdown(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; <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接口,我们看一下Executor接口的实现:
1 2 3 | public interface Executor { void execute(Runnable command); } |
到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
1 2 3 4 | execute() submit() shutdown() shutdownNow() |
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
二.深入剖析线程池实现原理
在上一节我们从宏观上介绍了ThreadPoolExecutor,下面我们来深入解析一下线程池的具体实现原理,将从下面几个方面讲解:
1.线程池状态
2.任务的执行
3.线程池中的线程初始化
4.任务缓存队列及排队策略
5.任务拒绝策略
6.线程池的关闭
7.线程池容量的动态调整
1.线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
1 2 3 4 5 | volatile int runState; static final int RUNNING =0; static final int SHUTDOWN =1; static final int STOP =2; static final int TERMINATED =3; |
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
2.任务的执行
在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务 private final ReentrantLock mainLock =new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小 //、runState等)的改变都要使用这个锁 private final HashSet<Worker> workers =new HashSet<Worker>(); //用来存放工作集
private volatile long keepAliveTime; //线程存活时间 private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间 private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列) private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler;//任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数 |
每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。
corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子:
假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。
因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;
当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
如果说新任务数目增长的速度远远大于工人做任务的速度,当队满了时,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;
然后就将任务也分配给这4个临时工人做;
如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。
这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。
也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。
不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。
ThreadPoolExecutor 核心实现原理
1. 线程池的处理流程
我们首先重点要看的是,如何执行提交的任务。我可以通过下图来看看。
总结描述下就是:
1. 判断核心线程池是否已满,如果不是,则创建线程执行任务
2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中
3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务
4. 如果线程池也满了,则按照拒绝策略对任务进行处理
submit 流程详解
当调用 submit 方法,就是向线程池中提交一个任务,处理流程如步骤1所示。但是我们需要更深入理解。
submit 方法是定义在 AbstractExecutorService 中,最终调用 ThreadPoolExecutor 的 execute 方法,即是模板方法模式的应用。
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
// 封装任务和返回结果为 RunnableFuture, 统一交由具体的子类执行
RunnableFuture<T> ftask = newTaskFor(task, result);
// execute 将会调用 ThreadPoolExecutor 的实现,是我们讨论的重要核心
execute(ftask);
return ftask;
}
// FutureTask 是个重要的线程池组件,它承载了具体的任务执行流
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// ThreadPoolExecutor 的任务提交过程
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// ctl 是一个重要的控制全局状态的数据结构,定义为一个线程安全的 AtomicInteger
// ctl = new AtomicInteger(ctlOf(RUNNING, 0));
int c = ctl.get();
// 当还没有达到核心线程池的数量时,直接添加1个新线程,然后让其执行任务即可,保证了核心线程数
if (workerCountOf(c) < corePoolSize) {
// 2.1. 添加新线程,且执行command任务
// 添加成功,即不需要后续操作了,添加失败,则说明外部环境变化了(可能已经将线程池关闭了)
if (addWorker(command, true))
return;
c = ctl.get();
}
// 当核心线程达到后,则尝试添加到阻塞队列中,具体添加方法由阻塞队列实现
// isRunning => c < SHUTDOWN;
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 2.2. 添加队列成功后,还要再次检测线程池的运行状态,决定启动线程或者状态过期
// 2.2.1. 当线程池已关闭,则将刚刚添加的任务移除,走reject策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 2.2.2. 当一个worker都没有时,则添加worker
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 当队列满后小于最大线程数,则直接再创建新的线程运行,如果不能再创建线程了,则 reject
else if (!addWorker(command, false))
// 2.3. 拒绝策略处理
reject(command);
}
通过上面这一小段代码,我们就已经完整地看到了。通过一个 ctl 变量进行全局状态控制,从而保证了线程安全性。整个框架并没有使用锁,但是却是线程安全的。
整段代码刚好完整描述了线程池的执行流程:
1. 判断核心线程池是否已满,如果不是,则创建线程执行任务;
2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中;
3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务;
4. 如果线程池也满了,则按照拒绝策略对任务进行处理;
2.1. 添加新的worker
一个worker,即是一个工作线程。
private boolean addWorker(Runnable firstTask, boolean core) {
// 为确保线程安全,进行CAS反复重试
retry:
for (;;) {
int c = ctl.get();
// 获取runState , c 的高位存储
// c & ~CAPACITY;
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 已经shutdown, firstTask 为空的添加并不会成功
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 更新worker+1数,成功则说明占位成功退出retry,后续的添加操作将是安全的,失败则说明已有其他线程变更该值
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// runState 变更,则退出到 retry 重新循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 以下为添加 worker 过程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 使用 Worker 封装 firstTask 任务,后续运行将由 Worker 接管
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 添加 worker 的过程,需要保证线程安全
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// SHUTDOWN 情况下还是会创建 Worker, 但是后续检测将会失败
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 既然是新添加的线程,就不应该是 alive 状态
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// workers 只是一个工作线程的容器,使用 HashSet 承载
// private final HashSet<Worker> workers = new HashSet<Worker>();
workers.add(w);
int s = workers.size();
// 维护一个全局达到过的最大线程数计数器
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// worker 添加成功后,进行将worker启起来,里面应该是有一个 死循环,一直在获取任务
// 不然怎么运行添加到队列里的任务呢?
if (workerAdded) {
// 将线程启动,创建线程时将worker以runnable作为参数传入
t.start();
workerStarted = true;
}
}
} finally {
// 如果任务启动失败,则必须进行清理,返回失败
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
// 大概添加 worker 的框架明白了,重点对象是 Worker, 我们稍后再讲
// 现在先来看看,添加失败的情况,如何进行
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
// ctl 中的 workerCount - 1 , CAS 实现
decrementWorkerCount();
// 尝试处理空闲线程
tryTerminate();
} finally {
mainLock.unlock();
}
}
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
// 停止可能启动的 worker
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 线程池正在运行、正在清理、已关闭但队列还未处理完,都不会进行 terminate 操作
if (isRunning(c) ||
// c >= TIDYING
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
// 停止线程的两个方式之一,只中断一个 worker,中断添加失败那个worker
interruptIdleWorkers(ONLY_ONE);
return;
}
// 以下为整个线程池的后置操作
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 设置正在清理标识
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 线程池已终止的钩子方法,默认实现为空
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
// 此处 termination 为唤醒等待关闭的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 迭代所有 worker
for (Worker w : workers) {
Thread t = w.thread;
// 获取到 worker 的锁之后,再进行 interrupt
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 只中断一个 worker, 立即返回, 不保证 interrupt 成功
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
2.2. 当添加队列成功后,若是发现线程池状态变更,需要进行移除队列操作
public boolean remove(Runnable task) {
// 此移除不一定能成功
boolean removed = workQueue.remove(task);
// 上面已经看过,它会尝试停止一个 worker 线程
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
3. 添加失败进行执行拒绝策略
final void reject(Runnable command) {
// 拒绝策略是在构造方法时传入的,默认为 RejectedExecutionHandler
// 即用户只需实现 rejectedExecution 方法,即可以自定义拒绝策略了
handler.rejectedExecution(command, this);
}
4. Worker 的工作机制
从上面的实现中,我们可以看到,主要是对 Worker 的添加和 workQueue 的添加,所以具体的工作是由谁完成呢?自然就是 Worker 了。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable // worker本身就是实现了Runnable
{
final Thread thread; // 包含一个线程对象
Runnable firstTask; // 储存外界提交的任务
/** Per-thread task counter */
volatile long completedTasks;
// Worker 的构造方法,主要是接受一个 task, 可以为 null, 如果非null, 将在不久的将来被执行
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 将 Worker 自身当作一个 任务,绑定到 worker.thread 中
// thread 启动时,worker 就启动了
this.thread = getThreadFactory().newThread(this);
}
// Worker 的主要工作实现,通过一个循环扫描实现
/** Delegates main run loop to outer runWorker */
public void run() {
// 调用 ThreadPoolExecutor 外部实现的 runWorker 方法
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 不停地从 workQueue 中获取任务,然后执行,就是这么个逻辑
// getTask() 会阻塞式获取,所以 Worker的线程往往不会立即退出
while (task != null || (task = getTask()) != null) {
// 执行过程中是不允许并发的,即同时只能一个 task 在运行,此时也不允许进行 interrupt
w.lock();
// 检测是否已被线程池是否停止 或者当前 worker 被中断
// STOP = 1 << COUNT_BITS;
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
// 中断信息传递
wt.interrupt();
try {
// 任务开始前 切点,默认为空执行
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 直接调用任务的run方法, 具体的返回结果,会被 FutureTask 封装到 某个变量中
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();
}
}
// 正常退出,有必要的话,可能重新将 Worker 添加进来
completedAbruptly = false;
} finally {
// 处理退出后下一步操作,可能重新添加 Worker
processWorkerExit(w, completedAbruptly);
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
// 在 Worker 正常退出的情况下,检查是否超时导致,维持最小线程数
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 如果满足最小线程要求,则直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 否则再添加一个Worker到线程池中备用
// 非正常退出,会直接再添加一个Worker
addWorker(null, false);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果进行了 shutdown, 且队列为空, 则需要将 worker 退出
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// do {} while (! compareAndDecrementWorkerCount(ctl.get()));
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 线程数据大于最大允许线程,需要删除多余的 Worker
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果开户了超时删除功能,则使用 poll, 否则使用 take() 进行阻塞获取
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 获取到任务,则可以进行执行了
if (r != null)
return r;
// 如果有超时设置,则会在下一循环时退出
timedOut = true;
}
// 忽略中断异常
// 在这种情况下,Worker如何响应外部的中断请求呢??? 思考
catch (InterruptedException retry) {
timedOut = false;
}
}
}
怎么取等待队列中的线程执行
所以,Worker的作用就体现出来了,一个循环取任务执行任务过程:
1. 有一个主循环一直进行任务的获取;
2. 针对有超时的设置,会使用poll进行获取任务,如果超时,则 Worker 将会退出循环结束线程;
3. 无超时的设置,则会使用 take 进行阻塞式获取,直到有值;
4. 获取任务执行前置+业务+后置任务;
5. 当获取到null的任务之后,当前Worker将会结束;
6. 当前Worker结束后,将会判断是否有必要维护最低Worker数,从而决定是否再添加Worker进来。
还是借用一个网上同学比较通用的一个图来表述下 Worker/ThreadPoolExecutor 的工作流程吧(已经很完美,不需要再造这轮子了)
5. shutdown 操作实现
ThreadPoolExecutor 是通过 ctl 这个变量进行全局状态维护的,shutdown 在线程池中也是表现为一个状态,所以应该是比较简单的。
public void shutdown() {
// 为保证线程安全,使用 mainLock
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// SecurityManager 检查
checkShutdownAccess();
// 设置状态为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲的 Worker, 即相当于依次关闭每个空闲线程
interruptIdleWorkers();
// 关闭钩子,默认实现为空操作,为方便子类实现自定义清理功能
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 再
tryTerminate();
}
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
// 自身CAS更新成功或者被其他线程更新成功
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
// 关闭空闲线程(非 running 状态)
private void interruptIdleWorkers() {
// 上文已介绍, 此处 ONLY_ONE 为 false, 即是最大可能地中断所有 Worker
interruptIdleWorkers(false);
}
与 shutdown 对应的,有一个 shutdownNow, 其语义是 立即停止所有任务。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 与 shutdown 的差别,设置的状态不一样
advanceRunState(STOP);
// 强行中断线程
interruptWorkers();
// 将未完成的任务返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
// 调用 worker 的提供的中断方法
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
// ThreadPoolExecutor.Worker#interruptIfStarted
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
// 直接调用任务的 interrupt
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
6. invokeAll 的实现方式
invokeAll, 望文生义,即是调用所有给定的任务。想来应该是一个个地添加任务到线程池队列吧。
// invokeAll 的方法直接在抽象方便中就实现了,它的语义是同时执行n个任务,并同步等待结果返回
// java.util.concurrent.AbstractExecutorService#invokeAll(java.util.Collection<? extends java.util.concurrent.Callable<T>>)
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
// 依次调用各子类的实现,添加任务
execute(f);
}
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
try {
// 依次等待执行结果
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
实现很简单,都是些外围调用。
7. ThreadPoolExecutor 的状态值的设计
通过上面的过程,可以看到,整个ThreadPoolExecutor 状态的依赖是非常强的。所以一个好的状态值的设计就显得很重要了,runState 代表线程池或者 Worker 的运行状态。如下:
// runState is stored in the high-order bits
// 整个状态使值使用 ctl 的高三位值进行控制, COUNT_BITS=29
// 1110 0000 0000 0000
private static final int RUNNING = -1 << COUNT_BITS;
// 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 0010 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
// 0100 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
// 0110 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// 整个状态值的大小顺序主: RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
// 而低 29位,则用来保存 worker 的数量,当worker增加时,只要将整个 ctl 增加即可。
// 0001 1111 1111 1111, 即是最大的 worker 数量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 整个 ctl 描述为一个 AtomicInteger, 功能如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
8. awaitTermination 等待关闭完成
从上面的 shutdown, 可以看到,只是写了 SHUTDOWN 标识后,尝试尽可能地中断停止Worker线程,但并不保证中断成功。要想保证停止完成,需要有另外的机制来保证。从 awaitTermination 的语义来说,它是能保证任务停止完成的,那么它是如何保证的呢?
// ThreadPoolExecutor.awaitTermination()
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
// 只是循环 ctl 状态, 只要 状态为 TERMINATED 状态,则说明已经关闭成功
// 此处 termination 的状态触发是在 tryTerminate 中触发的
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
看起来, awaitTermination 并没有什么特殊操作,而是一直在等待。所以 TERMINATED 是 Worker 自行发生的动作。
那是在哪里做的操作呢?其实是在获取任务的时候,会检测当前状态是否是 SHUTDOWN, 如果是SHUTDOWN且 队列为空,则会触发获取任务的返回null.从而结束当前 Worker.
Worker 在结束前会调用 processWorkerExit() 方法,里面会再次调用 tryTerminate(), 当所有 Worker 都运行到这个点后, awaitTermination() 就会收到通知了。(注意: processWorkerExit() 会在每次运行后进行 addWorker() 尝试,但是在 SHUTDOWN 状态的添加操作总是失败的,所以不用考虑)
到这里,大部分朋友应该对任务提交给线程池之后到被执行的整个过程有了一个基本的了解,下面总结一下:
1)首先,要清楚corePoolSize和maximumPoolSize的含义;
2)其次,要知道Worker是用来起到什么作用的;
3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
3.线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread():初始化一个核心线程;
- prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
1 2 3 4 5 6 7 8 9 10 | public boolean prestartCoreThread() { return addIfUnderCorePoolSize(null);//注意传进去的参数是null }
public int prestartAllCoreThreads() { int n =0; while (addIfUnderCorePoolSize(null))//注意传进去的参数是null ++n; return n; } |
注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的
1 | r = workQueue.take(); |
即等待任务队列中有任务。
4.任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5.任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
1 2 3 4 | ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 |
6.线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7.线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
使用示例
前面我们讨论了关于线程池的实现原理,这一节我们来看一下它的具体使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class Test { public static void main(String[] args) { ThreadPoolExecutor executor =new ThreadPoolExecutor(5,10,200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){ MyTask myTask =new MyTask(i); executor.execute(myTask); System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+ executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount()); } executor.shutdown(); } }
class MyTaskimplements Runnable { private int taskNum;
public MyTask(int num) { this.taskNum = num; }
@Override public void run() { System.out.println("正在执行task "+taskNum); try { Thread.currentThread().sleep(4000); }catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task "+taskNum+"执行完毕"); } } |
执行结果:
View Code
从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。
创建线程池方法
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
1 2 3 | Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池 Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池 |
下面是这三个静态方法的具体实现;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1,1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } |
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
四.如何合理配置线程池的大小
本节来讨论一个比较重要的话题:如何合理配置线程池大小,仅供参考。
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。