并发系列(6)之 ThreadPoolExecutor 详解
本文将主要介绍我们平时最常用的线程池 ThreadPoolExecutor ,有可能你平时没有直接使用这个类,而是使用 Executors 的工厂方法创建线程池,虽然这样很简单,但是很可能因为这个线程池发生 OOM ,具体情况文中会详细介绍;
二、ThreadPoolExecutor 概览
ThreadPoolExecutor 的继承关系如图所示:

其中:
- Executor:定义了
executor(Runnable command)异步接口,但是没有强制要求异步; - ExecutorService:提供了生命周期管理的方法,以及有返回值的任务提交;
- AbstractExecutorService:提供了
ExecutorService的默认实现;
1. 主体结构
public class ThreadPoolExecutor extends AbstractExecutorService {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 状态控制变量,核心
private final BlockingQueue<Runnable> workQueue; // 任务等待队列
private final HashSet<Worker> workers = new HashSet<Worker>(); // 工作线程集合
private volatile ThreadFactory threadFactory; // 线程构造工厂
private volatile RejectedExecutionHandler handler; // 拒绝策略
private volatile long keepAliveTime; // 空闲线程的存活时间(非核心线程)
private volatile int corePoolSize; // 核心线程大小
private volatile int maximumPoolSize; // 工作线程最大容量
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
...
}
这里已经可以大致看出 ThreadPoolExecutor 的结构了:

2. Worker 结构
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
final Thread thread; // 持有线程,只有在线程工厂运行失败时为空
Runnable firstTask; // 初始化任务,不为空的时候,任务直接运行,不在添加到队列
volatile long completedTasks; // 完成任务计数
Worker(Runnable firstTask) {
setState(-1); // AQS 初始化状态
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); // 循环取任务执行
}
...
// AQS 锁方法
}
这里很容易理解的是 thread 和 firstTask;但是 Worker 还继承了 AQS 做了一个简易的互斥锁,主要是在中断或者 worker 状态改变的时候使用;具体 AQS 的详细说明可以参考,AbstractQueuedSynchronizer 源码分析 ;
3. ctl 控制变量
ctl 控制变量(简记 c)是一个 AtomicInteger 类型的变量,由两部分信息组合而成(两个值互补影响,又可以通过简单的大小比较判断状态):
- 线程池的运行状态 (runState,简记 rs),由 int 高位的前三位表示;
- 线程池内有效线程的数量 (workerCount,简记 wc),由 int 地位的29位表示;
源码如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 用来表示线程数量的位数
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 线程最大容量
// 状态量
private static final int RUNNING = -1 << COUNT_BITS; // 高位 111,第一位是符号位,1表示负数
private static final int SHUTDOWN = 0 << COUNT_BITS; // 高位 000
private static final int STOP = 1 << COUNT_BITS; // 高位 001
private static final int TIDYING = 2 << COUNT_BITS; // 高位 010
private static final int TERMINATED = 3 << COUNT_BITS; // 高位 011
private static int runStateOf(int c) { return c & ~CAPACITY; } // 运行状态,取前3位
private static int workerCountOf(int c) { return c & CAPACITY; } // 线程数量,取后29位
private static int ctlOf(int rs, int wc) { return rs | wc; } // 状态和数量合成
private static boolean runStateLessThan(int c, int s) { return c < s; } // 状态比较
private static boolean runStateAtLeast(int c, int s) { return c >= s; }
private static boolean isRunning(int c) { return c < SHUTDOWN; } // RUNNING 是负数,必然小于 SHUTDOWN
代码中可以看到状态判断的时候都是直接比较的,这是因为 TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING ;他们的状态变迁关系如下:

其中:
- RUNNING:运行状态,可接收新任务;
- SHUTDOWN:不可接收新任务,继续处理已提交的任务;
- STOP:不接收、不处理任务,中断正在进行的任务
- TIDYING:所有任务清空,线程停止;
- TERMINATED:钩子方法,执行后的最终状态;
三、ThreadPoolExecutor 源码分析
1. 增加工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这里正常情况下,只要大于SHUTDOWN,则必然不能添加线程;但是这里做了一个优化,
// 如果线程池还在继续处理任务,则可以添加线程加速处理,
// SHUTDOWN 表示不接收新任务,但是还在继续处理,
// firstTask 不为空时,是在添加线程的时候,firstTask 不入队,直接处理
// workQueue 不为空时,则还有任务需要处理
// 所以连起来就是 rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()
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;
if (compareAndIncrementWorkerCount(c))
break retry; // 线程数增加成功,则跳出循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) // 如果线程状态改变时,重头开始重试
continue retry;
}
}
// 此时线程计数,增加成功
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) { // 线程创建失败时,直接退出
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
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;
}
2. 提交任务
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { // 如果小于核心线程,直接添加
if (addWorker(command, true)) return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { // 任务入队
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command)) // 再次检查,状态不是RUNNING的时候,拒绝并移除任务
reject(command);
else if (workerCountOf(recheck) == 0) // 这里是防止状态为SHUTDOWN时,已经添加的任务无法执行
addWorker(null, false);
}
else if (!addWorker(command, false)) // 任务入队失败时,直接添加线程,并运行
reject(command);
}
流程图如下:

所以影响任务提交的因数就有:
- 核心线程的大小;
- 是否为阻塞队列;
- 线程池的大小;
3. 处理任务
工作线程启动之后,首先处理 firstTask 任务(特别注意,这个任务是没有入队的),然后从 workQueue 中取出任务处理,队列为空时,超时等待 keepAliveTime ;
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 ((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); // 退出时清理
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 此处保证 SHUTDOWN 状态继续处理任务,STOP 状态停止处理
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 是否关闭空闲线程
if ((wc > maximumPoolSize || (timed && timedOut)) // 如果线程大于最大容量,或者允许关闭,且第一次没取到
&& (wc > 1 || workQueue.isEmpty())) { // 返回空,最后由 processWorkerExit 清理
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;
}
}
}
4. 停止线程池
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); // 检查停止权限
advanceRunState(SHUTDOWN); // 设置线程池状态
interruptIdleWorkers(); // 设置所有线程中断
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate(); // 继续执行等待队列中的任务,完毕后设置 TERMINATED 状态
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue(); // 清空所有等待队列的任务,并返回
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
可以看到 shutdownNow 只比 shutdown 多了,清空等待队列,但是正在执行的任务还是会继续执行;
四、拒绝策略
之前提到了,提交任务失败的时候,会执行拒绝操作,在 JDk 中为我们提供了四种策略:
- AbortPolicy:直接抛出
RejectedExecutionException异常,这是默认的拒绝策略; - CallerRunsPolicy:由调用线程本身运行任务,以减缓提交速度;
- DiscardPolicy:不处理,直接丢弃掉;
- DiscardOldestPolicy:丢弃最老的任务,并执行当前任务;
五、Executors 工厂方法
另外就是根据线程池参数的不同,Executors 为我们提供了4种典型的用法:
SingleThreadExecutor:单线程的线程池,提交任务顺序执行;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
如代码所示,就是最大线程、核心线程都是1,和无界队列组成的线程池,提交任务的时候就会,直接将任务加入队列顺序执行;
FixedThreadPool:固定线程数量线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
同 SingleThreadExecutor 一样,只是线程数量由用户决定;
CachedThreadPool:动态调节线程池;
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这里核心线程为0,队列是 SynchronousQueue 容量为1的阻塞队列,而线程数最大,存活60s,所以有任务的时候直接创建新的线程,超时空闲60s;
ScheduledThreadPool:定时任务线程池,功能同 Timer 类似,具体细节后续还会讲到;
总结
- 决定线程池运行逻辑的主要有三个变量,核心线程大小,队列容量,线程池容量
- 最后发现其实 Executors 提供的几种实现,都很典型;但是却容易发生 OOM ,所以最好还是自己手动创建比较好;
并发系列(6)之 ThreadPoolExecutor 详解的更多相关文章
- 【Java并发系列03】ThreadLocal详解
img { border: solid 1px } 一.前言 ThreadLocal这个对象就是为多线程而生的,没有了多线程ThreadLocal就没有存在的必要了.可以将任何你想在每个线程独享的对象 ...
- 高并发架构系列:Redis并发竞争key的解决方案详解
https://blog.csdn.net/ChenRui_yz/article/details/85096418 https://blog.csdn.net/ChenRui_yz/article/l ...
- C++11 并发指南三(std::mutex 详解)
上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::thread 的一些用法,并给出了两个小例子,本文将介绍 std::mutex 的用法. Mutex ...
- ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借
ASP.NET MVC深入浅出系列(持续更新) 一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...
- C++11 并发指南三(std::mutex 详解)(转)
转自:http://www.cnblogs.com/haippy/p/3237213.html 上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::th ...
- 【C/C++开发】C++11 并发指南三(std::mutex 详解)
本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别如下: C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇) C ...
- 分布式-技术专区-Redis并发竞争key的解决方案详解
Redis缓存的高性能有目共睹,应用的场景也是非常广泛,但是在高并发的场景下,也会出现问题:缓存击穿.缓存雪崩.缓存和数据一致性,以及今天要谈到的缓存并发竞争.这里的并发指的是多个redis的clie ...
- C++11 并发指南六(atomic 类型详解四 C 风格原子操作介绍)
前面三篇文章<C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)>.<C++11 并发指南六( <atomic> 类型详解二 std::at ...
- C++11 并发指南六(atomic 类型详解三 std::atomic (续))
C++11 并发指南六( <atomic> 类型详解二 std::atomic ) 介绍了基本的原子类型 std::atomic 的用法,本节我会给大家介绍C++11 标准库中的 std: ...
- C++11 并发指南六( <atomic> 类型详解二 std::atomic )
C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍) 一文介绍了 C++11 中最简单的原子类型 std::atomic_flag,但是 std::atomic_flag ...
随机推荐
- BZOJ_1691_[Usaco2007 Dec]挑剔的美食家_贪心
BZOJ_1691_[Usaco2007 Dec]挑剔的美食家_贪心 题意: 与很多奶牛一样,Farmer John那群养尊处优的奶牛们对食物越来越挑剔,随便拿堆草就能打发她们午饭的日子自然是一去不返 ...
- CentOS7 配置SVN服务器
也可以参考这里:https://jingyan.baidu.com/article/148a1921d84be34d71c3b18f.html 1.安装svn yum install -y subve ...
- C++ set用法以及迭代器用法
有关set的一些常用函数 1.begin() / end() 返回首/尾元素迭代器 2.rbegin() / rend() 返回尾/首元素反向迭代器,反向迭代器可以反向遍历容器的迭代器,从下面的程序已 ...
- Hibernate-ORM:05.Hibernate中的list()和iterator()
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- Hibernate中查多条(全部)使用list()或iterator() 本篇介绍: 1.如何使用list() ...
- 大白话5分钟带你走进人工智能-第十四节过拟合解决手段L1和L2正则
第十四节过拟合解决手段L1和L2正则 第十三节中, ...
- 数据库分片(Database Sharding)详解
本文由云+社区发表 作者:腾讯云数据库 Introduction 导言 任何看到显著增长的应用程序或网站,最终都需要进行扩展,以适应流量的增加.以确保数据安全性和完整性的方式进行扩展,对于数据驱动的应 ...
- appium-desktop录制脚本二次开发,生成我司自动化脚本
目的 通过对appium-desktop脚本录制功能进行二次开发,使录制的java脚本符合我司自动化框架要求. 实现步骤 1.增加元素名称的输入框 由于ATK(我司自动化测试框架)脚本中元素是以“ap ...
- 从壹开始微服务 [ DDD ] 之终篇 ║当事件溯源 遇上 粉丝活动
回首 哈喽~大家好,时间过的真快,关于DDD领域驱动设计的讲解基本就差不多了,本来想着周四再开一篇,感觉没有太多的内容了,剩下的一个就是验证的问题,就和之前的JWT很类似,就不打开一个章节了,而且这个 ...
- 将本地文件传输到GitHub
统一概念: 工作区:增删文件和内容 暂存区:键入命令 git add 改动的文件,此次改动就放到了 『暂存区』 本地仓库 :键入命令 git commit ,此次改动就放到了『本地仓库』,每个 com ...
- 【TCP协议】(3)---TCP粘包黏包
[TCP协议](3)---TCP粘包黏包 有关TCP协议之前写过两篇博客: 1.[TCP协议](1)---TCP协议详解 2.[TCP协议](2)---TCP三次握手和四次挥手 一.TCP粘包.拆包图 ...