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的用法] ...
随机推荐
- 201521123090 《Java程序设计》第4周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 继承与多态的概念与实现 父类与之类的关系 解决代码复用的办法 2. 书面作业 注释的应用 使 ...
- 201521123010 《Java程序设计》第9周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 常用异常: ①题目5-1 1.1 截图你的提交结果(出现学号) A: 1.2 ...
- Java 第十周总结
1. 本周学习总结 2. 书面作业 1.finally (题目4-2) 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? finally创建一个代码块.该代 ...
- 201521123070 《JAVA程序设计》第12周学习总结
1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对象(属性:int id, String name,int age,doubl ...
- linux(CentOS5.8)环境下搭建Radius
本文记录了freeRadius在CentOS5.8环境下的基本搭建过程,未涉及mysql的加入及配置 freeradius官方地址:http://freeradius.org/ 环境:CentOS5. ...
- Winfrom 简单的安卓手机屏幕获取和安卓简单操作
为啥我要做这个东西了,是因为经常要用投影演示app ,现在有很多这样的软件可以把手机界面投到电脑上 ,但都要安装,比如说360的手机助手,我又讨厌安装,于是就自己捣鼓了下 做了这个东西, 实现了以下简 ...
- 《MySQL必知必会》[03] 表数据的增删改
1.增:插入数据 INSERT关键字可以插入新的行到数据库表中: 插入完整的行 插入行的一部分 插入多行 插入某些查询的结果 基本的INSERT语句是: INSERT INTO R(A1, A2, . ...
- Hibernate @Embeddable注释
在hibernate中实现自定义类型,只要实现UserType接口即可或者以Component的形式提供.JPA的@Embedded注释可以在你的Entity中使用一般的Java对象,此对象需要用@E ...
- MongoDB中的映射,限制记录和记录拼排序 文档的插入查询更新删除操作
映射 在 MongoDB 中,映射(Projection)指的是只选择文档中的必要数据,而非全部数据.如果文档有 5 个字段,而你只需要显示 3 个,则只需选择 3 个字段即可. find() 方法 ...
- Angularjs –– Expressions(表达式)
点击查看AngularJS系列目录 转载请注明出处:http://www.cnblogs.com/leosx/ Angular的表达式 Angular的表达式和JavaScript代码很像,不过通常A ...