Java 1.7 ThreadPoolExecutor源码解析
Java中使用线程池技术一般都是使用Executors
这个工厂类,它提供了非常简单方法来创建各种类型的线程池:
- public static ExecutorService newFixedThreadPool(int nThreads)
- public static ExecutorService newSingleThreadExecutor()
- public static ExecutorService newCachedThreadPool()
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
核心的接口其实是Executor
,它只有一个execute
方法抽象为对任务(Runnable接口)的执行, ExecutorService
接口在Executor
的基础上提供了对任务执行的生命周期的管理,主要是submit
和shutdown
方法, AbstractExecutorService
对ExecutorService
一些方法做了默认的实现,主要是submit和invoke方法,而真正的任务执行 的Executor接口execute
方法是由子类实现,就是ThreadPoolExecutor
,它实现了基于线程池的任务执行框架,所以要了解 JDK的线程池,那么就得先看这个类。
再看execute
方法之前需要先介几个变量或类。
ctl
- private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
这个变量是整个类的核心,AtomicInteger
保证了对这个变量的操作是原子的,通过巧妙的操作,ThreadPoolExecutor用这一个变量保存了两个内容:
- 所有有效线程的数量
- 各个线程的状态(runState)
低29位存线程数,高3位存runState
,这样runState有5个值:
- RUNNING:-536870912
- SHUTDOWN:0
- STOP:536870912
- TIDYING:1073741824
- TERMINATED:1610612736
线程池中各个状态间的转换比较复杂,主要记住下面内容就可以了:
- RUNNING状态:线程池正常运行,可以接受新的任务并处理队列中的任务;
- SHUTDOWN状态:不再接受新的任务,但是会执行队列中的任务;
- STOP状态:不再接受新任务,不处理队列中的任务
围绕ctl变量有一些操作,了解这些方法是看懂后面一些晦涩代码的基础:
corePoolSize
核心线程池大小,活动线程小于corePoolSize则直接创建,大于等于则先加到workQueue中,队列满了才创建新的线程。
keepAliveTime
线程从队列中获取任务的超时时间,也就是说如果线程空闲超过这个时间就会终止。
Worker
- private final class Worker extends AbstractQueuedSynchronizer implements Runnable ...
内部类Worker
是对任务的封装,所有submit的Runnable都被封装成了Worker,它本身也是一个Runnable, 然后利用AQS框架(关于AQS可以看我这篇文章)实现了一个简单的非重入的互斥锁, 实现互斥锁主要目的是为了中断的时候判断线程是在空闲还是运行,可以看后面shutdown
和shutdownNow
方法的分析。

- // state只有0和1,互斥
- protected boolean tryAcquire(int unused) {
- if (compareAndSetState(0, 1)) {
- setExclusiveOwnerThread(Thread.currentThread());
- return true;// 成功获得锁
- }
- // 线程进入等待队列
- return false;
- }
- protected boolean tryRelease(int unused) {
- setExclusiveOwnerThread(null);
- setState(0);
- return true;
- }

之所以不用ReentrantLock是为了避免任务执行的代码中修改线程池的变量,如setCorePoolSize
,因为ReentrantLock是可重入的。
execute
execute
方法主要三个步骤:
- 活动线程小于corePoolSize的时候创建新的线程;
- 活动线程大于corePoolSize时都是先加入到任务队列当中;
- 任务队列满了再去启动新的线程,如果线程数达到最大值就拒绝任务。

- public void execute(Runnable command) {
- if (command == null)
- throw new NullPointerException();
- int c = ctl.get();
- // 活动线程数 < corePoolSize
- if (workerCountOf(c) < corePoolSize) {
- // 直接启动新的线程。第二个参数true:addWorker中会重新检查workerCount是否小于corePoolSize
- if (addWorker(command, true))
- // 添加成功返回
- return;
- c = ctl.get();
- }
- // 活动线程数 >= corePoolSize
- // runState为RUNNING && 队列未满
- if (isRunning(c) && workQueue.offer(command)) {
- int recheck = ctl.get();
- // double check
- // 非RUNNING状态 则从workQueue中移除任务并拒绝
- if (!isRunning(recheck) && remove(command))
- reject(command);// 采用线程池指定的策略拒绝任务
- // 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败
- else if (workerCountOf(recheck) == 0)
- // 这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
- // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
- addWorker(null, false);
- // 两种情况:
- // 1.非RUNNING状态拒绝新的任务
- // 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
- } else if (!addWorker(command, false))
- reject(command);
- }

注释比较清楚了就不再解释了,其中比较难理解的应该是addWorker(null, false);
这一行,这要结合addWorker一起来看。 主要目的是防止HUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
addWorker
这个方法理解起来比较费劲。
runWorker
任务添加成功后实际执行的是runWorker
这个方法,这个方法非常重要,简单来说它做的就是:
- 第一次启动会执行初始化传进来的任务firstTask;
- 然后会从workQueue中取任务执行,如果队列为空则等待keepAliveTime这么长时间。
getTask
processWorkerExit
线程退出会执行这个方法做一些清理工作。
tryTerminate
processWorkerExit方法中会尝试调用tryTerminate来终止线程池。这个方法在任何可能导致线程池终止的动作后执行:比如减少wokerCount或SHUTDOWN状态下从队列中移除任务。
shutdown和shutdownNow
shutdown这个方法会将runState置为SHUTDOWN
,会终止所有空闲的线程。

- public void shutdown() {
- final ReentrantLock mainLock = this.mainLock;
- mainLock.lock();
- try {
- checkShutdownAccess();
- // 线程池状态设为SHUTDOWN,如果已经至少是这个状态那么则直接返回
- advanceRunState(SHUTDOWN);
- // 注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit →
- // tryTerminate方法中会保证队列中剩余的任务得到执行。
- interruptIdleWorkers();
- onShutdown(); // hook for ScheduledThreadPoolExecutor
- } finally {
- mainLock.unlock();
- }
- tryTerminate();
- }

shutdownNow方法将runState置为STOP。和shutdown方法的区别,这个方法会终止所有的线程。

- public List<Runnable> shutdownNow() {
- List<Runnable> tasks;
- final ReentrantLock mainLock = this.mainLock;
- mainLock.lock();
- try {
- checkShutdownAccess();
- // STOP状态:不再接受新任务且不再执行队列中的任务。
- advanceRunState(STOP);
- // 中断所有线程
- interruptWorkers();
- // 返回队列中还没有被执行的任务。
- tasks = drainQueue();
- }
- finally {
- mainLock.unlock();
- }
- tryTerminate();
- return tasks;
- }

主要区别在于shutdown调用的是interruptIdleWorkers
这个方法,而shutdownNow实际调用的是Worker类的interruptIfStarted
方法:

- private void interruptIdleWorkers(boolean onlyOne) {
- final ReentrantLock mainLock = this.mainLock;
- mainLock.lock();
- try {
- for (Worker w : workers) {
- Thread t = w.thread;
- // w.tryLock能获取到锁,说明该线程没有在运行,因为runWorker中执行任务会先lock,
- // 因此保证了中断的肯定是空闲的线程。
- if (!t.isInterrupted() && w.tryLock()) {
- try {
- t.interrupt();
- } catch (SecurityException ignore) {
- } finally {
- w.unlock();
- }
- }
- if (onlyOne)
- break;
- }
- }
- finally {
- mainLock.unlock();
- }
- }


- void interruptIfStarted() {
- Thread t;
- // 初始化时state == -1
- if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
- try {
- t.interrupt();
- } catch (SecurityException ignore) {
- }
- }
- }

这就是前面提到的Woker类实现AQS的主要作用。
注意:shutdown方法可能会在finalize被隐式的调用。
这篇博客基本都是代码跟注释,所以如果不是分析ThreadPoolExecutor
源码的话看起来会非常无聊。
总结:
int corePoolSize:核心线程数
int maximumPoolSize:最大线程数
BlockingQueue workQueue:任务队列
long keepAliveTime:和TimeUnit unit一起构成线程的最大空闲时间,一旦超过该时间还没有任务处理,该线程就走向结束了。它是针对当前线程数已经超过corePoolSize核心线程数了或者核心线程数也开启超时策略,即属性allowCoreThreadTimeOut=true
ThreadFactory threadFactory:线程工厂
RejectedExecutionHandler handler:拒绝策略,当任务太多来不及处理,可拒绝该任务
先简单描述下ThreadPoolExecutor的execute(futureTask)过程的大概情况:
1 如果当前线程数小于corePoolSize,则直接创建出一个线程,用于执行新加进来的任务
2 如果当前线程数已经超过corePoolSize,则将该任务放到BlockingQueue workQueue任务队列中,该任务队列可以是有限容量也可以是无限容量的。每个线程处理完一个任务后,都会不断的从BlockingQueue workQueue任务队列中取出任务并执行
3 如果BlockingQueue workQueue是有限容量的,已满无法放进新的任务了,如果此时的线程数小于maximumPoolSize,则直接创建一个线程执行该任务
4 如果线程数已达到maximumPoolSize不能再创建线程了,则直接使用RejectedExecutionHandler handler拒绝该任务
原文转载:http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-ThreadPoolExecutor.html
Java 1.7 ThreadPoolExecutor源码解析的更多相关文章
- Java并发之ThreadPoolExecutor源码解析(二)
ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务.使用线程池可以帮助我们限定和整合程序资源 ...
- Java并发之ThreadPoolExecutor源码解析(三)
Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...
- ThreadPoolExecutor系列<三、ThreadPoolExecutor 源码解析>
本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.html 在源码解析前,需要先理清线程池控制的运行状态 ...
- ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析
ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...
- Java集合---Array类源码解析
Java集合---Array类源码解析 ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...
- java.lang.Void类源码解析_java - JAVA
文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerEx ...
- Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析
目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...
- 【Java并发编程】21、线程池ThreadPoolExecutor源码解析
一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...
- Java集合类:AbstractCollection源码解析
一.Collection接口 从<Java集合:整体结构>一文中我们知道所有的List和Set都继承自Collection接口,该接口类提供了集合最基本的方法,虽然List接口和Set等都 ...
随机推荐
- Java8 新特性之Stream API
1. Stream 概述 Stream 是Java8中处理集合的关键抽象概念,可以对集合执行非常复杂的查找,过滤和映射数据等操作; 使用 Stream API 对集合数据进行操作,就类似于使用 SQL ...
- 排序算法review<2>--Shell 排序
shell排序方法也是一种插入排序算法,于1959年由D.L.Shell提出,其基本方法是:首先将带排序文件分为d1(d1<n)组,将所有彼此之间间隔为d和d的倍数的记录放在一组中,然后在组内进 ...
- Python 类型和对象(转)
译文:http://wiki.woodpecker.org.cn/moin/PyTypesAndObjects 原文:http://www.cafepy.com/article/python_attr ...
- Mybatis框架学习总结-使用Mybatis对表执行CRUD操作
使用MyBatis对表执行CRUD操作——基于XML的实现 1.创建(create)用户:在userMapper.xml文件中增加: <!-- 创建用户Create --> <ins ...
- java7(1)——反编译深入理解增强的switch(读字节命令实战)
[本文介绍] 本文主要讲java_7 的改进switch的底层实现.反编译一个使用带String的switch的demo并一步步解析反编译出来的字节命令,从编译的角度解读switch的底层实现. [正 ...
- 永远也记不住的linux环境变量,库文件,头文件,交叉编译...
一.环境变量1.node-v4.9.1-linux-armv7l1)安装cp node-v4.9.1-linux-armv7l.tar.gz /usr/local/cd /usr/local/tar ...
- PAT 1093 Count PAT's[比较]
1093 Count PAT's (25 分) The string APPAPT contains two PAT's as substrings. The first one is formed ...
- SpringBoot的核心注解和配置
一.入口类和SpringBootApplication Spring Boot的项目一般都会有*Application的入口类,入口类中会有main方法,这是一个标准的Java应用程序的入口方法. @ ...
- 20165324《Java程序设计》第七周
20165324<Java程序设计>第七周 教材学习内容总结 第11章 JDBC与MySOLz数据库 MySQL数据库管理系统,简称MySQL. 使用步骤: 启动MySQL数据库服务 器建 ...
- Leetcode 357
没用过Leetcode刷题,只能按照自己的想法随便写写了 思路:1.第一位数有9种(除了0)可能,第二位数有9种(除了第一位)可能,第三位数有8种(除了前两位)可能,以此类推...9*8*7*...( ...