线程池原理讲解——ThreadPoolExecutor
【这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干】
说线程池前,先简单回顾一下线程的状态吧:
1、线程状态转换
线程的五种状态,及其转换关系:
2、线程创建方式
三种:两个接口一个类
两个接口:Runnable实现run(), callable实现call()
一个类:Thread实现run()
《阿里巴巴Java手册》中规定:
接下来进入正题:
3、线程池ThreadPoolExecutor实现原理
3.1、ThreadPoolExecutor中定义了几个线程池状态常量。
// 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;
- RUNNING:是运行状态,线程池可以接收新任务;
- SHUTDOWN:是在调用shutdown()方法以后处在的状态。表示不再接收新任务,但队列中的任务可以执行完毕;
- STOP:是在调用shutdownNow()方法以后的状态。不再接收新任务,中断正在执行的任务,抛弃队列中的任务;
- TIDYING:表示所有任务都执行完毕;
- TERMINATED:为中止状态,调用terminated()方法后,尝试更新为此状态;
3.2、 构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
}
- corePoolSize是线程池的核心线程数
- maximumPoolSize是线程池允许的最大线程数
- keepAliveTime为线程空闲时的存活时间
- unit是keepAliveTime的单位
- workQueue是用来保存等待被执行的线程的队列
- threadFactory,线程工厂,通常使用默认工厂,定义了线程名称生成规则。
- handler是当最大线程数和队列都满了以后,线程池的处理策略
3.3、Executors工厂类
JDK提供了Executors工厂类。这里面有几种实例化线程池的方法:
- CachedThreadPool():可缓存线程池,一个任务创建一个线程,最大线程数为Integer.MAX_VALUE,所以使用时要控制好并发数量,否则创建过多的线程会占用大量资源;
- FixedThreadPool():指定数线程池,所有任务只能使用数量固定大小的线程;
- SingleThreadExecutor():单一数线程池,即相当于大小为1的FixedThreadPool(),可保证提交任务的顺序执行;
- ScheduleThreadPool():周期性线程池,可固定时间执行,也可延时执行,最大线程数为Integer.MAX_VALUE,。
上述虽然很方便,但是!
但是《阿里巴巴Java手册》又说了:在开发中创建线程池时,最好不要使用Executor类中的初始化方法来创建,而是使用ThreadPoolExecutor类中的初始化来创建,这样的好处是能够让写的同学更明确线程池的运行规则,从而避免资源耗尽的风险。
而使用Executor返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
2)CachedThreadPool 和ScheduleThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
3.4、ThreadPoolExecutor执行任务的方法【最重要,也是线程池的核心原理】
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))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
代码看不懂不要紧,一个例子附上来:
假如我手里有500W RMB(哈哈哈,想暴富想疯了),俗话说“富不过三代”,心里想着不能座山吃空啊,还得好好想想经营之道来用钱生钱。
由于本人对吃的,对干饭比较感兴趣,于是乎,就准备开一个饭店。取名为“池饭的店”。我的饭店还有个特色就是一对一服务,一个厨子服务一桌顾客噢,哇好高级。
开饭店得有厨子对吧,那好,我斥“巨”资请来了5位大厨,,都烧得一手红烧肉(哈哈哈,吃肉肉也算特色)。
开业以后生意良好,但我巡逻发现好多顾客更喜欢吃辣的,为了赚更多钱,于是我又一狠心请了5位川菜大厨。
一段时间后,饭店生意越来愈好,顾客也越来越多。有厨子说得再加几个厨子才行,但是作为老板,我要赚钱啊,得控制成本(哈哈哈,资本的世界),我发现不能再招厨子了。因为光这10位常用厨子的开支都已经快占了收入的一半了。那厨子抱怨顾客多,忙不过来怎么办呢?哈哈哈,我说:“顾客多,那就让顾客排队等着,谁让你们几位大厨做的饭菜香呢(哈哈哈,霸气的同时也要拍一下大厨的马屁,得让他们舒舒服服地听我的话)”。
于是,当顾客多了后,没有空闲的厨子进行一对一服务了,多的顾客就排号等着。等哪位顾客吃完了,厨子服务空闲了,就叫下一桌。好办法,果然平时生意井然有序,厨子不慌不忙也还算可以。
但是呢?我巡逻又发现,周一到周四还可以,但是一到周末,饭店周围的大佬们都休息,但是又懒得做饭,就全部都涌来到我的饭店了,毕竟服务周到嘛。这时候有些顾客就不满意了,就抱怨人太多,等一次都要一个小时以上,不划算。于是,为了挽留住这些客源,我每到周末就从其他地方雇来10位临时厨子,过了周末再把他们辞了(不要怪我狠心,都是出来挣票票的)。这样周末就有了20位厨子,但平时周一周五还是原来10个厨子,毕竟不养闲人嘛。哈哈哈,好办法。
虽然这样有所缓解,大家周末排队的时间从1h减少为0.5h,但是有时候还是会顾客饱满,没办法啊,生意太好了,哈哈哈。那怎么办呢?我的饭店空间就这么大,不可能无限增加厨子啊,那好吧,为了不让厨子抱怨,不让厨子累着,那我只能忍痛割爱,残忍地将部分顾客拒之门外,同时赔送点小礼品,代金券。
哈哈哈,后来想了想,既然客源这么多,那为了赚更多票票,我就开分店吧,于是我带领我的员工走上了人生巅峰!
成功后,有人开始请我去讲解成功之道,我想了想,画了这个图:
哈哈哈,如果你了解了我这个开饭店的“成功之道”,那么线程池你也就理解了。如下:
好了,为了让大家更理解透彻,我给代码加上注释:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取clt,clt记录着线程池状态和运行线程数。
int c = ctl.get();
//运行线程数小于核心线程数时,创建线程放入线程池中,并且运行当前任务。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//创建线程失败,重新获取clt。
c = ctl.get();
}
//线程池是运行状态并且运行线程大于核心线程数时,把任务放入队列中。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//重新检查线程池不是运行状态时,
//把任务移除队列,并通过拒绝策略对该任务进行处理。
if (! isRunning(recheck) && remove(command))
reject(command);
//当前运行线程数为0时,创建线程加入线程池中。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//运行线程大于核心线程数时并且队列已满时,
//创建线程放入线程池中,并且运行当前任务。
else if (!addWorker(command, false))
//运行线程大于最大线程数时,失败则拒绝该任务
reject(command);
}
里面多次调用的addWorker()方法如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
//获取clt,clt记录着线程池状态和运行线程数。
int c = ctl.get();
//获取线程池的运行状态。
int rs = runStateOf(c); //线程池处于关闭状态,或者当前任务为null
//或者队列不为空,则直接返回失败。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; for (;;) {
//获取线程池中的线程数
int wc = workerCountOf(c);
//线程数超过CAPACITY,则返回false;
//这里的core是addWorker方法的第二个参数,
//如果为true则根据核心线程数进行比较,
//如果为false则根据最大线程数进行比较。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//尝试增加线程数,如果成功,则跳出第一个for循环
if (compareAndIncrementWorkerCount(c))
break retry;
//如果增加线程数失败,则重新获取ctl
c = ctl.get();
//如果当前的运行状态不等于rs,说明状态已被改变,
//返回第一个for循环继续执行
if (runStateOf(c) != rs)
continue retry;
}
} boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//根据当前任务来创建Worker对象
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;
}
各位看官,理解否?
Over.....
参考:
线程池原理讲解——ThreadPoolExecutor的更多相关文章
- Java多线程系列——线程池原理之 ThreadPoolExecutor
ThreadPoolExecutor 简介 ThreadPoolExecutor 是线程池类. 通俗的讲,它是一个存放一定数量线程的线程集合.线程池允许多个线程同时运行,同时运行的线程数量就是这个线程 ...
- Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理
相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...
- 12.ThreadPoolExecutor线程池原理及其execute方法
jdk1.7.0_79 对于线程池大部分人可能会用,也知道为什么用.无非就是任务需要异步执行,再者就是线程需要统一管理起来.对于从线程池中获取线程,大部分人可能只知道,我现在需要一个线程来执行一个任 ...
- juc线程池原理(二):ThreadPoolExecutor的成员变量介绍
概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...
- Java ThreadPoolExecutor线程池原理及源码分析
一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...
- 含源码解析,深入Java 线程池原理
从池化技术到底层实现,一篇文章带你贯通线程池技术. 1.池化技术简介 在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能. 在编程领域,比较典型的池化技术有: 线程池.连接池.内存池 ...
- java多线程系类:JUC线程池:03之线程池原理(二)(转)
概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包 ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
随机推荐
- 利用iptables防火墙保护web服务器
实例:利用iptables防火墙保护web服务器 防火墙--->路由器-->交换机-->pc机 配置之前,清空下已有的规则,放在规则冲突不生效 工作中,先放行端口写完规则,再DROP ...
- 1.5V升3V芯片和电路图,DC-DC升压IC
1.5V升3V的升压芯片,3V给LED供电,或者单片机模块供电等. PW5200A工作频率为1.4MHZ.轻载时自动PWM/PFM模式切换,提高效率. PW5200A能够提供2.5V和5V之间的可调输 ...
- Markdown特殊字符、数学公式汇总
引自:https://blog.csdn.net/weixin_39653948/article/details/104621249
- 【JeecgBoot】关于 jeecg-boot 的项目理解、使用心得和改进建议
工欲善其事,必先利其器. 脚手架选型 一年前,我接到为团队落地一个快速开发脚手架的任务. 在月底这节骨眼上,时间紧,任务急,有想自己撸一个脚手架的人都赶紧把这想法收起来吧!这劳民又伤身的事咱肯定是不能 ...
- 大数据系列2:Hdfs的读写操作
在前文大数据系列1:一文初识Hdfs中,我们对Hdfs有了简单的认识. 在本文中,我们将会简单的介绍一下Hdfs文件的读写流程,为后续追踪读写流程的源码做准备. Hdfs 架构 首先来个Hdfs的架构 ...
- javascript通过递归改子节点数据-用于层级深度未知的树形结构
最近在做这么个需求:树形结构,层级深度未知,一旦某个节点的状态是置灰的话,其所有子节点都要置灰. 方案一(数据库有值):如果数据库里置灰节点的所有子节点,值也都是"置灰",那后台取 ...
- Correct the classpath of your application so that it contains a single, compatible version of org.thymeleaf.spring5.SpringTemplateEngine
Error starting ApplicationContext. To display the conditions report re-run your application with 'de ...
- SICP 解题集 — SICP 解题集 https://sicp.readthedocs.io/en/latest/
SICP 解题集 - SICP 解题集 https://sicp.readthedocs.io/en/latest/
- loj10009钓鱼___vector的调试
题目描述 在一条水平路边,有 n 个钓鱼湖,从左到右编号为1,2,...,n .佳佳有 h 个小时的空余时间,他希望利用这个时间钓到更多的鱼.他从1 出发,向右走,有选择的在一些湖边停留一定的时间( ...
- Python骚操作从列表推导和生成器表达式开始
序列 序列是指一组数据,按存放类型分为容器序列与扁平序列,按能否被修改分为不可变序列与可变序列. 容器序列与扁平序列 容器序列存放的是对象的引用,包括list.tuple.collections.de ...