ThreadPoolExecutor
多线程编程基本上都要用到线程池。首先为什么需要线程池,而不是每次都new一条线程?
- 任务与线程解耦
- 复用线程,减小创建和销毁的开销
- 利于管理线程,线程是一种创建、维护和销毁都要比较谨慎的资源
看下架构。
创建线程池,有哪几种方式,优缺点
看原理之前先了解如何使用。线程池通常通过Executors这个工具类来创建,这样比较方便。或者如果有更复杂的需求就直接通过线程池的构造方法创建。或者直接使用ForkJoinPool。
- Executors.newFixedThreadPool(int nThreads) :创建一个拥有 n个线程的定长线程池
- 执行长期的任务,性能好很多
- 创建一个定长线程池,可控制线程数最大并发数,超出的线程会在队列中等待
- Executors.newSingleThreadExecutor:创建一个只有1个线程的线程池
- 所有提交到线程池的任务需要串行执行的场景
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
- Executors.newCacheThreadPool(); 创建一个可扩容的线程池
- 比较灵活,当任务重时自动扩容,自动回收空闲线程
- Executors.newWorkStealingPool; 创建一个基于WorkStealing机制的线程池,其实用的就是ForkJoinPool
WorkStealing是线程协作的一种方式,线程如果完成了自己的工作可以从其他工作线程窃取工作,可以更有效的利用CPU,利用线程,因为线程本身是稀缺资源,大多数时候WorkStealing模式都是比较实用的。
从最顶层接口开始,看看结构是怎么样的。
Executor
Executor接口只有一个excute方法,就是一个可以执行任务的执行器。
ExecutorService
- 提交异步任务后返回的是Future
- 提供了一些管理该执行器的方法
AbstractExecutorService
ExecutorService的一个实现模板抽象类。稍微看看各个方法。
submit
- 提交一个异步任务,可以是runnable或者callable
- 通过newTaskFor方法将runnable或callable封装成FutureTask,FutureTask继承了RunnableFuture接口。
- 调用excute执行这个异步任务
- 返回这个FutureTask,这样就完成了提交一个任务,返回一个Future.
而核心的excute方法,则交由子类实现,也就是具体如何执行这些提交的任务,就要看下面的线程池了。
ThreadPoolExecutor
ThreadPoolExecutor继承于AbstractExecutorService,直接来看名称是线程池执行器,用线程池来执行提交的任务。一般也直接叫做线程池。线程池如何创建,如何管理,excute方法具体如何利用线程池来执行FutureTask,这些都是需要思考的问题,
线程池用一个32位的int来表示状态,其中高3位表示运行状态(总共5种),低29位表示线程数。
运行状总共有以下5种,这也代表了线程池的整个生命周期。
各个运行状态的行为:
- running: 接收新任务,同时处理队列中的任务
- shutdown: 不再接收新任务,但仍然会处理队列中的任务
- stop: 不再接收新任务,不处理队列中的任务,并且中断正在执行的任务
- tidying: 此时所有任务都结束了,workerCount变量为0,会调用terminated这个钩子方法,主要进行一些整理工作
- terminated: terminated方法结束之后,就进入该状态/
各个状态的转换:
线程池通过阻塞队列暂存提交的任务。
worker保存于一个hashset中,为了互斥访问还配备了一把互斥锁。
hashset不是线程安全的,为什么不使用CurrentHashMap而是用一个hashset和一把互斥锁呢?因为使用CurrentHashMap可能会引起虚假唤醒,而且有其他地方也需要用到锁。
线程池重要参数
- 核心线程数
- 最大线程数
- keepAliveTime: 线程空闲时间,超过这个时间非核心线程会被回收
- TimeUnit: 时间单位
- 任务队列
- 线程工厂
- 拒绝策略
这些参数都比较重要,应该记住。还有就是这些参数如何影响线程池的运行的要了解一下。
- 创建线程池并提交任务后,如果线程数 < corePoolSize,则直接创建一条新的核心线程放入线程池,并将该任务传给它执行。
- 否则将任务加入任务队列
- 如果任务入队失败了,说明此时已经不是running状态,或者队列满了,拒绝该任务。
Worker
线程池管理了一些worker,真正执行任务的是worker。
worker中封装有一条线程用来执行任务;和一个firstTask(线程池传入的第一个任务),woker以继承AQS的方式实现了一个简易的互斥、不可重入的锁;
worker实现Runnable接口,可以把worker看成一个Runnable:
Worker的run方法就是将自己作为参数调用线程池的runWorker()
worker启动后,首先会执行firstTask,然后会不断的从任务队列中拉取任务。
worker为什么要继承AQS来实现互斥锁呢?Worker要对什么进行同步?
主要是为了确保只有一个线程在执行worker的行为。
worker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt(); // 如果线程池状态为STOP但当前线程没有中断,则中断当前线程
try {
beforeExecute(wt, task); 执行之前的行为,默认为空,是一个hook
try {
task.run();
afterExecute(task, null); 执行之后的行为,默认为空,是一个hook
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null; // 当前task已经执行完毕
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
官方文档对Worker的描述如下:
Class Worker主要维护运行任务的线程的中断控制状态,以及其他次要的状态。此类适当地扩展AbstractQueuedSynchronizer,以简化获取和释放每个任务执行的锁的过程。这可以防止旨在唤醒等待任务的工作线程而不是中断正在运行的任务的中断。我们实现了一个简单的不可重入互斥锁,而不是使用ReentrantLock,因为我们不希望工作任务在调用诸如setCorePoolSize之类的池控制方法时能够重新获取锁。此外,为了在线程实际开始运行任务之前抑制中断,我们将锁状态初始化为负值,并在启动时清除它(在runWorker中)。
我们从向线程池提交任务开始,逐步分析。假设一开始线程池是空的。
- 提交任务后线程池会调用execute(Runnable command) 来执行这个任务
- 如果此时worker数量小于核心线程数,则调用addWorker(Runnable firstTask, boolean core)方法增加worker,并将任务作为firstTask参数传递过去
addWorker(Runnable firstTask, boolean core)
首先先判断线程池当前的状态是否能够增加一个worker。
我们知道线程池如果处于SHUTDOWN状态下会仅仅处理旧的任务不接收新的任务,如果线程池处于STOP状态下连旧的任务也不会处理。
- 如果处于STOP状态,直接返回false,不要再添加worker了
- 如果是SHUTDOWN状态,仍然有旧的任务需要处理,但不能再添加新任务进来了,这里就判断一下如果firstTask不为null,返回false
- 如果是SHUTDOWN状态而且工作队列为空,也不应该添加worker,因为这个状态下仅仅处理旧的任务,而任务队列已经空了。
然后首先因为要新增一个worker,自旋CAS改变状态。
接下来这段代码就是创建worker的逻辑了。
目的是创建一个worker,加入workers,并让worker开始工作,如果成功则返回true否则返回false。
注意全局锁mainLock的获取保证了在此期间状态不会改变。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) { // 由于指令重排可能导致未初始化完成就返回引用w,使得t == null
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 获取全局锁之后再次确认状态,确保状态满足添加worker的条件,并且在持有锁的期间状态不会改变。
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW) // 检查worker中的线程状态是否为NEW
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) { // 确保真正创建了worker而且成功添加入workers之后才启动worker
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)// workerStarted指明了是否能够成功添加worker并启动
addWorkerFailed(w);
}
return workerStarted;
addWorkerFailed,这个方法从workers中移除worker,由于添加worker可能导致本应该终止的线程池没有终止,所以这里如果addWorker失败了要尝试终止线程池,如果此时线程池满足终止条件就终止了。