ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析
ThreadPoolExecutor 源码解析
本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.html
在源码解析前,需要先理清线程池控制的运行状态,以及运行状态之间的任务调度
线程池控制状态(ctl ,原子操作 ,来自包java.util.concurrent.atomic ,保证线程并发安全),
分为两大类:workerCount(当前运行的线程数) runState(当前线程的运行状态)
1、runState运行状态:
a> RUNNING : 接受新的任务以及处理队列中的任务;
b> SHUTDOWN : 不接受新的任务,但是可以处理队列任务;
c> STOP : 不接受新的任务,不处理队列任务,中断正在处理的任务;
d> TIDYING : 所有任务已经终止,workerCount = 0(当前不存在运行的线程数),线程运行状态转变为TIDYING,
并将调用terminated() 钩子方法;
e> TERMINATED : terminated()已经完成;
2、任务调度:runState(当前线程的运行状态) 顺序转换:
a> RUNNING -> SHUTDOWN 调用shutdown()方法(也许隐含在finalize())
b> (RUNNING or SHUTDOWN) -> STOP 调用shutdownNow()
c> SHUTDOWN -> TIDYING 当队列无任务以及线程池无线程
d> STOP -> TIDYING 线程池无线程
e> TIDYING -> TERMINATED terminated()已经执行完成
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl 线程池控制状态,定义为int原子包装类AtomicInteger
作用:线程安全
(AtomicInteger 类其实就是Unsafe类中相关方法的封装;
Unsafe类提供了硬件级别的原子操作,对于并发来说,线程都是安全的,且锁的级别是乐观锁,比synchronized悲观锁性能高;
java不能直接访问操作系统底层,而是通过本地方法(native)来访问;)
线程池控制状态的转换是通过位运算实现,如下代码所示
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits
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; // Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
代码中几个关键全局变量,都是原子操作(AtomicInteger),保证线程安全
wc(work count 初始工作线程数量)
rs(run state 运行状态)
ctl(control 控制状态)
RUNNING <0 ,其他几个状态都是 >=0
wc(work count 初始工作线程数量) = RUNNING & CAPACITY = 0(是不是很巧妙)
rs(run state 运行状态) < 0 => RUNNING
public void execute(Runnable command) {...}
1、 检查当前运行状态
int c = ctl.get(); AtomicInteger方法中value定义的volatile类型(轻量级锁)
volatile作用:有序性(线程并发时保证程序代码执行有序性,防止重排序)
可见性(任何线程工作内存的共享变量改变,会保证主内存同步可见)
单一操作原子性
2、 运行状态为Running时(正常进行操作的前提)
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
当前活动线程数量 < 核心线程池数量(手动配置)时,开启新的线程,任务不进队列,直接扔给线程处理,
在addWorker方法中会判定当前的运行状态,addWorker()核心方法会在后续阐述。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
当前活动线程数量 > 核心线程池数量时,新的任务才开始准备入队列workQueue.offer(command),而入队列的前提是当前运行状态必须是Running,
而不是Shutdown,Stop等等其他状态,所以需要先判定isRunning(c)。
进入队列后需要重新检测当前线程的运行状态(double-check),线程出现如下两种情况的对应处理策略:
1> 如果当前线程突然die掉(线程异常或pool异常),remove(command)队列去除当前任务,回滚到上一个任务状态,
reject(command) 对当前任务的处理方式见说明第6点(new task 被拒绝(rejected)处理策略);
2> workerCountOf(recheck) == 0 当前没有活跃线程,addWorker(null, false);仅仅表示开启一个新的线程;
else if (!addWorker(command, false))
reject(command);
当前活动线程数量 > 核心线程池数量 且 workQueue已满,重新逐步开启线程,开启线程总量为maximumPoolSize - corePoolSize,
如果当前工作线程 >= maximumPoolSize,则新任务的处理方式见说明第6点(new task 被拒绝(rejected)处理策略);
注意core 为true or false 状态对应的处理策略;
注:后面两种状态说明当前任务入队的速度要快于当前配置下任务处理的速度。
private boolean addWorker(Runnable firstTask, boolean core) {...}
addWorker主要作用是判定运行状态,确定是否开启线程,处理任务,只有在以下3种状态下才会调用:
a> 当前工作线程数量 < corePoolSize ,线程数量没有达到最小要求的线程数量,新进任务需要开启新的线程处理(即使线程池有空闲线程);
b> 当前工作线程数量 >= corePoolSize,且workQueue队列已满,需要开启新的线程来处理任务;
c> 当前工作线程数量 >= maximumPoolSize 新进任务不开启线程,进入被拒绝(rejected)处理策略;
addWorker()方法具体代码如下:
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty())) //运行状态在SHUTDOWN级别以上(STOP,TIDYING,TERMINATED),任务直接丢弃;
return false; for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) //wc(工作线程) 超过指定线程的最大值,任务直接丢弃;
return false; //core 为true前提是当前工作线程 < corePoolSize
//core 为fasle前提是当前工作线程 < maximumPoolSize
// 如果超过,说明应用程序发生了异常,新进入的任务执行就没有意义;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
1> 判定当前线程的运行状态,在SHUTDOWN级别以及之上
(除了在SHUTDOWN时workQueue队列中还有任务没有处理,因为SHUTDOWN定义 : 不接受新的任务,但是可以处理队列任务),直接返回false;
2> 当前工作线程数量超出线程容量或者超出corePoolSize : maximumPoolSize中一种(core确定),
返回false(决定新的任务进入workQueue队列或者被拒绝(rejected)处理策略)
3> 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; /* ReentrantLock具有公平和非公平两种模式,也各有优缺点:
公平锁是严格的以FIFO的方式进行锁的竞争,但是非公平锁是无序的锁竞争,刚释放锁的线程很大程度上能比较快的获取到锁,队列中的线程只能等待,所以非公平锁可能会有“饥饿”的问题。但是重复的锁获取能减小线程之间的切换,而公平锁则是严格的线程切换,这样对操作系统的影响是比较大的,所以非公平锁的吞//吐量是大于公平锁的,这也是为什么JDK将非公平锁作为默认的实现。
ReentrantLock源码解析见:http://www.cnblogs.com/zhimingyang/p/5702752.html
*/
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //运行在RUNNING状态,新创建线程实例入队列
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {//在新创建线程实例成功进入队列后,线程开始处理任务
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) //任务开始标识false,线程队列去除该线程,线程状态并终止
addWorkerFailed(w);
}
在当前运行状态为Running情况下,
w = new Worker(firstTask) Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
通过线程工厂创建线程实例,开启线程,并同时将第一个任务扔给线程处理,
见 public void run() {
runWorker(this);
}
runWorker(this)方法后续阐述
当前开启的活跃线程实例需要保存,以备下次重复利用,这里用private final HashSet<Worker> workers = new HashSet<Worker>()保存,
且在添加时需要加锁 mainLock.lock(),防止访问共享数据冲突,保证线程安全。
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();
try {
beforeExecute(wt, task);//任务处理前可以进行一些准备工作,打印日志,统计时间等等,类似spring切面编程思想。如果有需要,此方法需要重写;
Throwable thrown = null;
try {
task.run();//任务真正处理的地方,task实例对象是ThreadPoolTask 实现接口Runnable,调用对象ThreadPoolTask的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);//任务处理后可以进行一些准备工作,打印日志,统计时间等等,类似spring切面编程思想。如果有需要,此方法需要重写
}
} finally {
task = null;//任务处理完毕
w.completedTasks++;//统计当前线程完成的任务数量
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);//统计活跃线程完成的任务数量;去除workers(hashset结构,保存当前活跃线程)中当前线程;结束当前线程
}
1> Worker 继承AbstractQueuedSynchronizer,重写tryAcquire,tryRelease方法,采用的是AbstractQueuedSynchronizer的非公平锁机制(nonfairTryAcquire)
查看ReentrantLock源代码,你会发现也是继承AbstractQueuedSynchronizer;
2> while (task != null || (task = getTask()) != null)
i> 每次开启一个线程,每个线程的第一个任务不进队列,直接处理,之后阻塞,直到拿到新的任务;
处理流程:第一个任务进来时 ,调用runWorker方法,task不为null,流程忽略(task = getTask()) != null语句,处理第一个任务,处理完毕后
task 为null
ii> getTask()方法是个阻塞方法,循环获取工作队列任务,源代码如下:
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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
} int wc = workerCountOf(c); // Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;//判定线程池(Workers)线程无任务时最小空闲数,allowCoreThreadTimeOut为true 则最小空闲数可为0,否则为corePoolSize大小(结合keepAliveTime) if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {//存在活跃线程且任务队列为空
if (compareAndDecrementWorkerCount(c))
return null;
continue;
} try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();//workQueue数组中去除当前任务,返回下一个任务,workQueue数组中没有任务,则一直阻塞
if (r != null) //如果下一个任务不为null,返回继续处理
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
a> 阻塞是通过workQueue实例实现(ArrayBlockingQueue继承BlockingQueue 多线程并发阻塞队列,关于BlockingQueue具体机制请参考
http://www.cnblogs.com/esther-qing/p/6492299.html)
阻塞的含义是:某一线程插入数据前,判定共享队列数组是否已满,如果已满full,则调用Condition接口的await方法,线程挂起,直到收到唤醒
(signal)通知(说明队列已有空闲空间),然后执行入队操作;某一线程删除数据前,判定共享队列数组是否已空empty,如果已空,则调用Condition接口的await方法,线程挂起,直到收到唤醒
(signal)通知(说明队列已有数据),然后执行出队操作;
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS): 在指定的等待时间内获取队列头部数据并移除此队列的头部,如果超出等待时间,返回null,
不执行移除操作,在等待时间内,线程挂起(通过Condition接口中等待方法(await)与通知方法(signal),Condition接口介绍以及使用见
http://www.cnblogs.com/jalja/p/5895051.html,本文具体实现在ArrayBlockingQueue类)
workQueue.take():获取队列头部数据并移除此队列的头部,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
3> processWorkerExit方法功能有3种:
i> 统计线程完成的任务数量(指所有活跃线程处理完的任务);
ii> 去除workers(hashset结构,保存当前活跃线程)中当前线程(执行此方法,说明当前线程在获取任务时,任务队列已无任务,
超过keepAliveTime时间,且
allowCoreThreadTimeOut || wc > corePoolSize 为true)
iii> 结束当前线程(调用tryTerminate(),终止线程);
iv> 在调用此方法时,任务队列有任务进来(! workQueue.isEmpty()= true),如果线程池没有活跃线程,则重新开启线程
addWorker(null, false),通过gettask获取任务;
源代码及相应注释如下:
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)) {//运行状态在STOP之前(RUNNING,SHUTDOWN)
if (!completedAbruptly) {//线程结束是否正常
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;//线程池中无任务时最小空闲线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);//开启一个新的线程,处理workQueue中未处理任务
}
}
不要让懒惰占据你的大脑,不要让妥协拖垮你的人生。青春就是一张票,能不能赶上时代的快车,你的步伐掌握在你的脚下。
ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析的更多相关文章
- Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之StampedLock源码解析
问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...
- 死磕 java同步系列之ReentrantReadWriteLock源码解析
问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...
- Mybatis 系列10-结合源码解析mybatis 的执行流程
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列8-结合源码解析select、resultMap的用法
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列7-结合源码解析核心CRUD 配置及用法
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列5-结合源码解析TypeHandler
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列4-结合源码解析节点:typeAliases
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
随机推荐
- [HEOI/TJOI2016]序列
Description: 给你一个序列,每个数可能变化为另一个数,每次最多有一个数变化 求最长的子序列,无论如何变化,这个子序列都不下降 Hint: \(n \le 10^5\) Solution: ...
- BZOJ 4804
辣鸡题目毁我青春 易推 \[\sum_{i=1}^n\sum_{i=1}^m \varphi(gcd(i,j))=\sum_{T}\frac{n}{T}\dfrac{m}{T}\sum_{d|T} \ ...
- Python函数式编程之lambda表达式
一:匿名函数的定义 lambda parameter_list: expression 二:三元表达式 条件为真时返回的结果 if 条件判断 else 条件为假的时候返回的结果 三:map map(f ...
- Springboot关于脚本脚本启动的项目:
#!/bin/bash if [ -f ~/.bash_profile ];then . ~/.bash_profilefi JAVA_HOME=/usr/local/usr_software/jd ...
- linux 使用sh@d0ws0cks server
[root@linux-node1 ~]# cat /etc/shadowsocks.json { "server":"x.x.x.x", , "lo ...
- Three.js学习笔记01
1.四大组件: 场景:场景是所有物体的容器 var scene = new THREE.Scene(); 相机: 正投影相机:远处的和近处的是一样大 THREE.OrthographicCamera ...
- 麒麟子Cocos Creator实用技巧
大家好,我是麒麟子, 开源棋牌<幼麟棋牌-四川麻将>(泄漏版叫 <达达麻将>)作者,成都幼麟科技创始人. 自09年进入游戏行业以来,不知不觉已经度过了十个春秋. 曾经我也血气方 ...
- 整理4种Vue组件通信方式
整理4种Vue组件通信方式 重点是梳理了前两个,父子组件通信和eventBus通信,我觉得Vue文档里的说明还是有一些简易,我自己第一遍是没看明白. 父子组件的通信 非父子组件的eventBus通信 ...
- 自学Python的经验之谈,学好Python的捷径
其实python非常适合初学者入门.相比较其他不少主流编程语言,有更好的可读性,因此上手相对容易.自带的各种模块加上丰富的第三方模块,免去了很多“重复造轮子”的工作,可以更快地写出东西.配置开发环境也 ...
- toastr操作完成提示框
toastr.js组件 关于信息提示框,项目中使用的是toastr.js这个组件,这个组件最大的好处就是异步.无阻塞,提示后可设置消失时间,并且可以将消息提示放到界面的各个地方. 官方文档以及源码 源 ...