线程池
- 因为创建和销毁线程是比较耗费资源的,所以出现了线程池,能够复用已经创建的对象
- 能够减低资源消耗
- 能够提高性能,不需要new线程,线程池里面的线程是可以服用的,不需要每次执行任务都重新创建和销毁
- 方便线程管理,线程是不饿能随便滥用的,如果不停创建,会导致系统崩溃,而线程池内的线程数量是一定的
Executor框架
- JKD提供了关于Executor接口的一系列接口、抽象类,我们把这些称为线程池的Executor框架,Executor框架本质上是一个线程池
Executor框架的接口与类结构
- java.util.concurrent(并发编程的工具)
- java.util.concurrent.atomic(变量的线程安全的原子性操作)
- java.util.concurrent.locks(用于锁定和条件等待同步等)
- Executor
- ExecutorService
- AbstractExecutorService
- ThreadPoolExecutor
- ForkJoinPool
- ScheduleExecutorService
- ScheduleThreadPoolExecutor
- AbstractExecutorService
- ExecutorService
ThreadPoolExecutor参数
- int corePoolSize :指定线程池中的核心数量(最少的线程个数),线程池中会维护一个最小的线程数量,即使他们都处于空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut()方法,默认情况下,创建线程池后,里面是没有线程的,只有当线程任务提交了以后,才会创建线程,如果需要创建线程池后马上创建线程,有以下两种方式
- prestartCoreThread();初始化一个核心线程
- prestartAllCoreThread;初始化全部核心线程
- BlockingQueue workQueue:当核心线程全部繁忙时,又提交了Runnable任务了,就会被放到这个队列里面
- int maximumPoolSize:指定线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,知道总线程数达到maximumPoolSize这个上限
- long keepAliveTime:线程空闲超时时间,如果一个线程处于空闲状态,并且线程数大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁
- TimeUnit unit:超时的时间单位
- ThreadFactory threadFactory :线程工厂,用于创建线程,一般默认即可,也可以自己写
- 默认只有这两个
- DefaultThreadFactory()和PrivilegedThreadFactory()
- **RejectedExecutionHandler handler:**拒绝策略,当任务太多来不及处理时,如何拒绝任务
- JDK内置了4中拒绝策略,都在java.util.concurrent包下面
- **AbortPolicy:**默认的策略,当任务不能再提交的时候抛出异常
- **DiscardPolicy:**直接丢弃任务,不跑出异常,就算出了问题,开发者也不知道(用的少)
- **DiscardOldestPolicy:**丢弃任务中最前面的任务,并执行当前任务(用的少)
- **CallereRunsPolicy:**交由任务的调用线程来执行当前任务,这种策略能够让所有任务都执行,适合大量计算类型的任务执行,使用这种策略的目标是为了让每个任务都能执行完毕,但是我们使用多线程是为了增大吞吐量而已,(线程池满了以后,我让main线程来执行)
- 总结:
- AbortPolicy异常终止策略:异常中止(无特殊场景)
- DiscardPolicy丢弃策略:无关紧要的任务(文章点击量+1/商品浏览量+1这种任务)
- DiscardOldestPolicy弃老策略:允许丢掉老数据的场景
- CallerRunsPolicy调用者运行策略:不允许失败场景(对性能要求不高、并发量较小的功能)
- 除了这四种策略,还可以通过自定义
RejectedExecutionHandler接口
,实现自定义拒绝策略
java
public class MyRejectedExecutionHandler implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor){
executor.getQueue().offer(r, 60, TimeUnit.SECONDS );
}
}
线程池拒绝策略应用实践
- 现在有一个发送短信验证码的需求,由于是营销活动,获取验证码的请求量可能比较大,如果发送短信验证码的操作提交到一个线程池中执行,如果某时刻,获取的验证码请求太多,导致线程池无法处理,就会触发线程池拒绝策略,为了让用户都获得验证码,当线程池处理不过来的时候,希望给重试的机会,所以就自定义了一种线程池拒绝策略
- 工作流程:
- 当线程池处理不过来的线程,触发了拒绝策略后,将来不及处理的任务放到Redis中,然后通过定时任务将Redis中的任务读取并运行
- Spring Boot启动
java
@SpringBootApplication
public class Application{
public static void main(String[] args){
ApplicationContext ioc = SpringApplication.run(Application.class);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) ioc.getBean("threadPoolExecutor");
for(int i = 0; i < 15; i++){
threadPoolExecutor.execute(new MyRunnable(i));
}
}
}
- 线程任务
java
public class MyRunnable implements Runnable, Serializable{
private int i;
public MyRunnable(int i){
this.i = i;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + ":" + this.i);
}
}
java
public class MyRejectedExecutionHandler implements RejectedExecutionHandler{
private RedisTemplate<Object, Object> redisTemplate;
public MyRejectedExecutionHandler(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
public void rejectedExeciton(Runnable r, ThreadPoolExecutor executor){
// 1.记录日志(我使用控制台,也可以使用log4j2)
System.err.println("Task " + r.toString() + " rejected from " + executor.toString());
// 2.将任务持久化到Redis中
redisTemplate.opsForList.leftPush("task:key", r);
}
}
- 配置类
java
@Configuration
public class Config{
@Bean
public ThreadPoolExecutor threadPoolExecutor(RedisTemplate<Object, Object> redisTemplate){
return new ThreadPoolExecutor(
1,
1,
15,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
Executors.defaultThreadFactory(),
new MyRejectedExecutionHandler(redisTemplate)
);
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
- 编写定时任务
java
@EnbaleScheduling
@Component
public class MyTask{
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Scheduled(cron = "0/5 * * * * ?")
public void task(){
Runnable r = (Runnable) redisTemplate.opsForList().rightPop("task:key");
if(r != null){
threadPoolExecutor.execute(r);
System.our.println("定时任务执行完成");
}
}
}
线程池的实现原理
线程池实现源代码
构造方法
- 线程池的构造方法一共有四个,分别带着5、6、6、7个参数,具体参数详解在上一节,现在对这四个构造函数进行一一分析
- 五参数线程池构造方法(除了线程工厂、拒绝策略剩下都传了)
java
public class ThreadPoolExecutor{
// 五参数构造方法
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
) {
// 通过this调用类里面的另外一个构造方法
// 很明显能够看到,这另外一个构造方法传了7个参数
// 也就是说,调用5参数的构造方法,最后调用的也是7个参数的构造方法
// 如果没有传入线程工厂或者拒绝策略,都会调用ThreadPoolExecutor中的默认实现
// 拒绝策略为AbortPolicy,工厂为默认工厂
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
}
- 六参数构造方法
java
public class ThreadPoolExecutor{
// 六参数构造方法(这个构造方法没有传拒绝策略)
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory
) {
// 调用7个参数构造方法
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 六参数构造方法(这个构造方法没有传线程工程)
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
) {
// 调用7个参数构造方法
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
}
- 七参数构造方法
java
public class ThreadPoolExecutor{
// 七参数构造方法调用了全部所需要传的
// 不管是5个参数还是6个参数,最后都会调用这个构造方法
// 所以这个构造方法是关键方法,接下来就看它做了什么
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 检查核心线程是否至少能有一个
// 线程池中线程最大数量是否至少有1个
// 核心线程数是否还要多过线程池所能拥有的最大数量
// 非核心线程空闲后销毁时间是否大于0
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
// 如果有任何一条不符合,直接抛异常
throw new IllegalArgumentException();
// 检查阻塞队列、线程工厂、拒绝策略是否为空
if (workQueue == null || threadFactory == null || handler == null)
// 如果有任何一个为空,直接抛异常
throw new NullPointerException();
// 在确认参数没问题后,开始为成员变量赋值
// 第一个是JDK内置的安全策略,感兴趣可自行了解
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;
}
}
execute方法
- 在我们需要让线程池执行一个任务的时候,往往会需要execute()方法执行线程任务,接下来就具体分析execute方法的源码
java
public class ThreadPoolExecutor{
public void execute(Runnable command) {
// 检查传入任务是否为空
if (command == null)
// 如果为空抛异常
throw new NullPointerException();
// 到这里表示任务肯定不为空
// 通过不知道什么东西得到了一个int值
// 那么就对这个ctl很好奇了,看看这个东西是什么,跟入源码
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);
}
}
- 通过跟入execute方法,只看了几行,就发现有我们不知道什么东西的
ctl
,全程和键盘的ctl键一样,是control
,接下来分析ctl
java
public class ThreadPoolExecutor{
// 列出了一些关键成员变量
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;
// 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
// 上面这句英文是源码自带的,意思是:包装和拆包ctl
// 什么包装拆包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; }
}
- 其实
ctl
是一个将线程池的运行状态
和线程池线程运行数量
结合在一个变量的值,通过一个Integer变量能够识别线程池的两个状态,至于为什么将两个状态合二为一的原因,在后面再进行讲解,我们都知道一个Integer其实是有32位bit,这32位bit中需要含有两个状态,先看看线程的运行状态有几种 - 下面成员变量列出来了几个状态值
RUNNING
、SHUTDOWN
、STOP
、TIDYING
、TERMINATED
,一共5个状态(可能有的人好奇,我们学线程的时候不是有六个状态吗,为什么这里只有五个,那些阻塞状态什么的到哪里去了,这里状态是线程池
的状态,而不是线程的状态),5个状态很明显需要3个二进制位才能完全表示,那这3个二进制位在Integer的哪个部分?高位?低位?还是中间随便找一个位置放3位? - 源码中有一句英文注释,上面给大家翻译了:
运行状态被存储在高的比特位
,也就是说高3位是运行状态,那么就好解释ctl
是什么东西了
ctl
是一个原子类的Integer,它一共32位bit,高3位是线程运行状态,剩下的29位是线程运行的数量
- 下面的部分是解释线程池是怎么样根据
ctl
识别到线程的运行状态的 - 先说明线程池运行状态的具体意思**(重要,和后面看源码息息相关,还有记住他们的大小,running最小依次递增)**
- RUNNING :运行状态,能够接收 并处理队列中的任务
- SHUTDOWN :线程池不再接受新任务,但会处理阻塞队列中的任务
- STOP :线程池不再接受新任务,也不会处理阻塞队列的任务,同时中断正在进行的任务
- TIDYING:线程池不再接受新任务,所有任务都终止,线程数量为0
- TERMINATED:线程池已经完全终止,不再有任何活动的线程
java
RUNNING
|
V
SHUTDOWN -----> TIDYING -----> TERMINATED
| ^
V |
STOP -------------|
- 线程池的状态值码
- RUNNING看源码能够看到,它是根据二进制的移位来实现的
在计算机的世界里面,所有的数字都是补码,且高的第一位是符号位,由0表示正数,1表示负数 -1的原码表示为:100000000 00000000 00000000 00000001 补码的计算根据计算机计算是:从右边往左数找到第一个1,然后1不变,从1左边第一个数字开始由0变1,1变0,但是最高的符号位不变(或者用反码+1都可以,我所说的方式是计算机电路设计底层的方式的转换) -1转换为补码为:11111111 11111111 11111111 11111111 所以RUNNING的状态值是:-1向右比特位移动COUNT_BITS这么多位 COUNT_BITS也是一个成员变量,它的值根据源码也能看到是Integer的大小-3,这个3就是我们高三位的状态值,为什么是减3而不是减其它数字的原因就是这个,也就是32-3=29 RUNNING的二进制值就是-1的补码直接右移29位,得到的数字就是: 11100000 00000000 00000000 00000000 下面的另外四种状态也是相同的道理,有时间可以自己推导: RUNNING = 111 00000 00000000 00000000 00000000 SHUTDOWN = 000 00000 00000000 00000000 00000000 STOP = 001 0000000 00000000 00000000 00000000 TIDYING = 010 00000 00000000 00000000 00000000 TERMINATED = 01100000 00000000 00000000 00000000
- 为什么将
线程池的运行状态
和线程池线程运行数量
两个状态合二为一? - 因为这里是线程池,有多个线程一起执行,很容易出现线程安全问题,那么保证线程安全一般会有两种方式:synchronized 锁和cas+volatile它们 使用的场景分别是写多读少 和写少读多 ,因为线程池的状态和线程数量变化的频率没有那么频繁,所以是使用cas+volatile的方式解决,并且运行状态和线程数量往往需要统一,不能出现一个修改,另一个确没有修改的情况,所以源码采用了原子类的方式,保证这两个值是同时变的。
- 那么
ctl
是以什么方式查看到五种状态以及查看线程池的线程数目的呢? - 线程池在每次改变
线程池的运行状态
和线程池线程运行数量
的时候,都会使用ctlOf(int rs, int wc)
这个方法,第一个参数表示运行的状态,第二个参数表示线程运行数量,就拿刚初始化ctl作为例子
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 明显看得到,线程池是被初始化为了:运行状态、运行线程为0,那么ctl是如何进行计算的? rs | wc,是直接进行或操作,或操作就是一个是1就是1,两个都0才为0 rs运行状态经过上面的分析,我们知道就是最高的3位,数量是低29位 111 00000 00000000 00000000 00000000 | 00000000 00000000 00000000 00000000 很明显拼合起来的值就是高三位保留,结果是11100000 00000000 00000000 00000000
- 当程序运行过程中,能够通过
runStateOf(int c)
方法获取线程池的运行状态,通过workerCountOf(int c)
获得线程池运行数量,这里的c参数在程序中传入的都是经过ctlOf()计算过的值,与上面的ctlOf()方法类似,只是用的是与操作,两个为1才为1,一个为0就是0,因为在CAPACITY
这个变量根据上面分析就知道是29个1,高三位都是0,如果我们想要获取数量,就可以直接忽视高三位,因为是与操作,都为1才为1,所以它只负责了低29位,如果想要获取状态,看到源码使用了~CAPACITY
表示取反操作,也就是高3位为1,低29位为0,也就是它与传进来的ctlOf()进行与操作,相与起来只会保留高三位,剩下29位肯定都是0
例子: int c = ctlOf(RUNNING, 1); // 设置运行状态位RUNNING,线程数有1个 int rs = runStateOf(c); int rc = workerCountOf(c);
c的计算: 111 00000 00000000 00000000 00000000 | 00000000 00000000 00000000 00000001 结果:11100000 00000000 00000000 00000001 rs计算: 111 00000 00000000 00000000 00000001 & 111 00000 00000000 00000000 00000000 结果:11100000 00000000 00000000 00000000 这个结果与RUNNING状态值一样,所以能判断状态 rc计算: 11100000 00000000 00000000 00000001 $ 00011111 11111111 11111111 11111111 结果:00000000 00000000 00000000 00000001 转换为10进制,表示1,所以能够知道线程数量
这里对成员变量的解析就告一段落,回到execute()方法中去
java
public class ThreadPoolExecutor{
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 上次分析到这里,现在知道ctl是什么了
// 这个c表示就是获取到的线程池状态和线程池中线程的数量
int c = ctl.get();
// 这里的workerCountOf(c),很明显是想要获得线程池中线程运行数量
// 判断线程池中的线程数量是否 < 核心线程数,如果小于就添加核心线程数量
if (workerCountOf(c) < corePoolSize) {
// 进来了表示线程池中运行的线程数量 一定是< 核心线程数
// 这里使用函数addWorker(线程任务, true)
// 这里只能知道它是想添加一个核心线程,具体怎么添加的还不知道,稍后继续分析
if (addWorker(command, true))
// 添加核心线程成功后直接返回
return;
// 走到这里表示没有创建成功,重新获取一下ctl
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);
}
}
- 分析到这里我们能够知道,我们每次通过execute方法提交线程任务的时候,它都会进行判断
当前线程池线程运行数量
是否达到我们设置的核心线程数
,如果没达到,就创建一个核心线程(但其实线程池内部不区分核心线程和非核心线程,这个true和false只是一个给线程池创建线程的一个条件)
比如 ThreadPoolExecutor创建了一个有3个核心线程的线程池 ,使用线程池执行第一个任务时,线程池才会为我创建一个核心线程,而不是3个核心线程,当第一个任务结束的时候,我再次提交一个任务,那么线程池会为我创建第二个核心线程,提交第三个后,才会创建满全部的核心线程
也就是线程池按需加载,也不难想象,创建核心线程的过程必然是需要上锁的,不然可能会导致核心线程高于我们设置的核心线程
java
public class 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();
}
// 从这里继续分析
// 这里判断 线程池是否为运行状态,如果是运行状态,就将任务提交到阻塞队列中去
// 为什么这么判断?
// 因为线程池的5中状态中,只有RUNNING状态能够接受新任务
if (isRunning(c) && workQueue.offer(command)) {
// 能够进来这个if,就是表示将核心线程满了,并且任务也提交到阻塞队列中去了
// 并且线程池还在运行状态
// 这表示任务的处理方式就是等什么时候核心线程有空了,从阻塞队列中取出来执行
// 再次检查ctl状态值,防止已提交的线程关闭了整个线程池,这里却还在执行(高并发情况下)
int recheck = ctl.get();
// 对线程池是否运行状态进行检查
if ( !isRunning(recheck) && remove(command))
// 进来这个if表示线程池不是运行状态,并且同时移除阻塞队列的任务成功
// 表示别的线程关了线程池了(除了running状态以外,剩下状态无法接受任务)
// 使用我们的拒绝策略,拒绝提交任务
reject(command);
// 到这里保证线程池还是在运行的,再判断线程池中线程数量是否为0
else if (workerCountOf(recheck) == 0)
// 进来了这个if表示线程池数量0
// 这个表示可能核心线程也被回收了,并且再上面的判断出现了异常,没有创建核心线程
// 为了保证线程池的活性,它会创建一个非核心线程处理任务
// addWorker(任务, 创建线程是否为核心线程)true为核心,false为非核心
addWorker(null, false);
}
// 如果到了这个if要么就是线程池没有运行 或者 阻塞队列满了,没有成功提交任务
else if (!addWorker(command, false))
// 阻塞队列满了,根据流程可以知道,需要创建非核心线程进行处理,如果没有创建成功
// 可能是阻塞队列满了,也可能是别的原因
// 执行拒绝策略
reject(command);
}
}
- 通过这个execute方法的执行流程可以知道,当提交一个任务,核心线程先去处理,处理不了就放到阻塞队列,放入阻塞队列如果没有别的什么异常,直接结束execute方法,等待后续线程池执行
- 如果放入阻塞队列出现异常,可能是别的线程关闭了线程池,那么采用任务拒绝的方式反馈,并且会移除阻塞队列的任务
- 如果放入阻塞队列出现异常,也可能是线程池线程数为0(这种情况可能是对线程池设置了核心线程回收命令),从这一段可以知道,哪怕没有创建核心线程的情况下,也有可能先创建的是非核心线程执行任务
总结:execute方法,解释了我们在使用线程池的时候任务的提交过程是什么样的,流程多是因为判断多!
addWorker方法
- execute方法只是让我们明白了我们通过线程池提交的任务是怎么样的,但是我们还是不清楚线程是怎么创建的(核心线程创建\非核心线程创建)
java
public class ThreadPoolExecutor{
/**
* @Param firstTask 提交的任务
* @Param core 创建是否为核心线程 true为核心线程,false为非核心线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
// 标记,也可以看作循环,和汇编方式是一样的,Java也有这种编程方式,只是不推荐使用
// 用法和汇编是一样的,感兴趣可以了解一下
retry:
// 外层死循环
for (;;) {
// 获取ctl
int c = ctl.get();
// 获取线程池状态
int rs = runStateOf(c);
// 对线程池状态再次进行检查
// 为什么说再次呢,因为创建核心线程的时候,其实没有检查过它的状态
// 只有创建非核心线程的时候检查过一次
// shutdown表示只能处理阻塞队列中的任务,但是不能提交新的任务
// 而只有running状态值比shutdown小
// 这里判断状态如果是shutdown以上的状态直接返回
// 如果是shutdown状态,但是没有提交任务,并且阻塞队列不为空
// 上面两个条件,刚好是shutdown能够做的事情,所以如果不满足上面任何一条,就直接返回创建失败
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
// 进来这里面表示,线程池运行状态大于shutdown。这种情况是完全处理不了任务
// 或者线程池运行状态为shutdown,但是提交了任务
// 或者线程池运行状态为shutdown,但是阻塞队列为空
return false;
// 内层死循环
// 到这里表示要么running、要么shutdown
for (;;) {
// 获取线程运行数量
int wc = workerCountOf(c);
// 先判断线程数量是否少于线程池的低29位能容纳的要求(肯定符合的,没有设置线程池这么大)
// 再判断想创建的是核心线程还是非核心线程
// 并且判断它们是否符合创建条件(创建后是否满足用户自己设置的核心线程数,以及最大线程数)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
// 不满足,创建失败
return false;
// 到这里对ctl的值+1,表示创建了一个线程
if (compareAndIncrementWorkerCount(c))
// 如果成功创建了线程的话退出整个循环
break retry;
// 到这里表示没有创建成功线程,重新拿到ctl
c = ctl.get();
// 看看线程池状态与刚来的时候是否相同(是否还是running或shutdown)
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 {
// 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);
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;
}
}
- 这一部分讲了线程的创建,在创建之前先检查了一下线程池的状态,确认是running或者shutdown的话,那就创建一个线程,如果没有创建成功,就检查是不是线程池的状态变了,然后重新来尝试创建,简单理解来说,就是状态检测
java
public class ThreadPoolExecutor{
/**
* @Param firstTask 提交的任务
* @Param core 创建是否为核心线程 true为核心线程,false为非核心线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
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();
if (runStateOf(c) != rs)
continue retry;
}
}
// 只有创建成功了线程才能到下面
// 这里记录两个状态值,worker状态和worker是否被添加
boolean workerStarted = false;
boolean workerAdded = false;
// 这里创建了一个新Worker,但其实,我们都不知道这个worker是什么东西,稍后分析
Worker w = null;
try {
// 这里初始化了Worker,并且将我们提交的任务放入构造函数
w = new Worker(firstTask);
// 从Worker里面拿了一个thread的实例变量
final Thread t = w.thread;
// 这里开始判断获取到worker的实例变量
// 但是我们对worker啥都不知道,这下不得不看了
// 下面先不看!!!!
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;
}
}
- 到这里,因为不知道Worker是什么东西,里面有什么,所以只能先分析到这里,先分析Worker
java
public class ThreadPoolExecutor{
// Worker是一个内部类,并且实现了Runnable,也就是它也可以是线程
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
// 构造方法
Worker(Runnable firstTask) {
// 设置这个线程不可被中断
// 这个不可中断的设置在线程池快要执行之前会放开,先记住
setState(-1);
// 将我们提交的线程任务赋值给成员变量
this.firstTask = firstTask;
// 通过线程工厂创建一个新的线程出来
// 这个线程工厂就是我们创建线程池的时候传入的线程工厂(可以自己创建的)
this.thread = getThreadFactory().newThread(this);
}
// 线程run方法
public void run() {
runWorker(this);
}
}
- 可以看到,通过构造方法,为thread赋值,通过工厂对象进行创建,我是用的DefaultThreadFactory进行分析,如果用了别的,也是一样的道理,这个worker其实就是线程池里面的核心线程和非核心线程
java
static class DefaultThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
// 创建了一个Thread线程,执行的任务是r,这个r就是我们创建的Worker,它是一个实现Runnable的类
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
// 在这里直接返回
return t;
}
}
- 可能有人被绕晕了,到时候线程是怎么执行的,Worker传进去,而不是我们提交的任务传进去,其实代码就相当于
public class Worker implements Runnable{ public void run(){ mission(); } public static void main(String[] args){ Worker worker = null; worker = new Worker(); Thread t = new Thread(worker); t.start(); } } 执行的还是Worker里面的run方法,并不是直接执行我们提交的任务 所以需要通过Worker里面的run方法执行我们提交的任务
- 回到代码
java
public class ThreadPoolExecutor{
/**
* @Param firstTask 提交的任务
* @Param core 创建是否为核心线程 true为核心线程,false为非核心线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
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;
// 更改ctl的值
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
// 只有创建成功了线程才能到下面
// 这里记录两个状态值,worker状态和worker是否被添加
boolean workerStarted = false;
boolean workerAdded = false;
// 这里创建了一个新Worker,但其实,我们都不知道这个worker是什么东西,稍后分析
Worker w = null;
try {
// 这里初始化了Worker,并且将我们提交的任务放入构造函数
w = new Worker(firstTask);
// 从Worker里面拿了一个thread的实例变量
final Thread t = w.thread;
// 判断从线程工厂里面创建的线程是否为空
if (t != null) {
// 这里在任务执行前上个锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 得到线程池状态
int rs = runStateOf(ctl.get());
// 如果状态时running或者shutdown并且没有提交任务才能执行任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 再判断从线程工厂那里获得的线程是否执行
// 这里我个人觉得没有必要判断,因为肯定是没有执行的,如果知道原因的话可以在评论区补充
if (t.isAlive())
throw new IllegalThreadStateException();
// 添加到一个workers队列中去
// 这个workers是真正的线程池,里面放满了worker,也就是线程池里面的线程
workers.add(w);
// 拿到线程池的长度
int s = workers.size();
// 如果长度大于历史最大,那么为他重新赋值
if (s > largestPoolSize)
largestPoolSize = s;
// 表示添加成功
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果添加成功
if (workerAdded) {
// 开启线程,执行worker里面的run方法
t.start();
// 给一个woker正在执行的标记
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
}
总结:线程池的任务其实是由一个Worker对象进行处理的,而线程池的话是Workers的一个Set集合进行存储的,真正的线程池就是Workers ,并且线程池是没有区分核心线程和非核心线程 ,他只要保留好Worker就好了,到这里线程池已经将所提交的任务放入了Workers,并且已经启动了线程,开启了线程,就表示和原来的main方法已经不同了,main方法接着继续执行它的 ,线程池执行我们提交的任务
线程池是如何执行提交的任务的
- 到这里已经结束了main方法的执行 ,就是在我们执行完execute后的全步骤,剩下的部分是线程池怎么样执行我们提交的任务,所以接下来的篇幅就是线程池是怎么样执行我们提交的任务的
- 看到Worker的run方法
java
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
public void run() {
runWorker(this);
}
}
java
public class ThreadPoolExecutor{
final void runWorker(Worker w) {
// 始终记住Worker就是线程,可以是核心线程,也可以是非核心线程
// 线程池里面没有对他们进行区分,他只要保留我们设置的值就可以了
// 获取当前线程
Thread wt = Thread.currentThread();
// 取出我们放在Worker里面的任务
Runnable task = w.firstTask;
// 为Worker中我们提交的的任务置为null(这一步感觉没必要)
w.firstTask = null;
// 还记得吗,初始化Worker的时候,我们设置了不可以被中断,到这里解锁了
w.unlock();
// 这里提供一个标记,表明执行线程是不是意外退出
// 如果任务不是正常完成,就不会修改这个值
boolean completedAbruptly = true;
try {
// 如果从上面获取的Worker任务为空的话,就去阻塞队列一直拿任务来执行
// 只有第一次会执行我们的Worker任务,以后就是进阻塞队列拿任务了
// 如果有跟着debug的话,当队列里面没有任务,执行到这里卡住是正常情况的,后面会分析是为什么
while (task != null || (task = getTask()) != null) {
w.lock();
// 检测ctl状态查看是否合法
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 {
// 将我们提交的任务置为null
// 这样第二次访问的就是阻塞队列中的值
task = null;
w.completedTasks++;
w.unlock();
}
}
// 执行到这里将标记变为正常完成,没有被中断
// 如果有跟着debug的话,到上面while循环觉得要跳出后,却没有到这里,是正常情况
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
}
总结:执行Worker的run方法就是执行我们提交的任务,在我们提交的任务的前后,可以自定义切面,在任务执行前后做操作,因为这里相对于前面逻辑没有那么乱,也就不画流程图了
线程池是如何实现线程的回收利用的
- 其实到这里对线程池的讲解也差不多了,知道了线程池是如何接收我们提交的任务的 ,知道了线程池是如何执行我们提交的任务的 ,但是我觉得还有一个点没有讲到,就是线程池是如何回收非核心线程的
- 如果跟着debug,会发现在阻塞队列中获取任务的时候会发现任务跟丢了,现在来解释以下为什么会有这种情况,这种情况同时也是线程池实现线程回收的机制,就是getTask方法
java
public class ThreadPoolExecutor{
// 因为从阻塞队列中获取任务执行这一步与线程复用息息相关,所以先从这里开始
private Runnable getTask() {
// 这里上来就是一个是否超时的标记,很重要,但是现在看不懂,不知道是做什么的
boolean timedOut = false;
// 这里是自旋,表示不断的从阻塞队列中取出任务
// 两种情况会退出循环
// 1.获取到任务了
// 2.非核心线程执行完了并且阻塞队列没东西(上面timeOut标记立大功)
for (;;) {
int c = ctl.get();
// 检测线程池状态
int rs = runStateOf(c);
// 如果线程池是stop以上的,进入if
// 或者线程池是shutdown,但是队列为空,进入if,剩下的情况不进入
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 这里会减少一个线程,通过ctl的方式进行减少
decrementWorkerCount();
// 因为stop及其以上不能执行任何任务,所以返回null
// 或者shutdown的时候,但是阻塞队列为空,也是返回null
return null;
}
// 到这里表示是可以执行线程池的状态(running/shutdown且阻塞队列还有东西)
// 获取线程数量
int wc = workerCountOf(c);
// 这个也是一个标记,表示是否需要回收线程(核心线程和非核心线程一起判断的)
// 其中allowCoreThreadTimeOut这个值表示是否运行核心线程回收
// 如果在线程池设置的时候设置true表示回收,如果false表示不回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果 线程数 > 线程池最大线程数
// 如果 超时且需要回收线程
// 两个只要有一个成立 并且线程池里面只要超过一个线程或阻塞队列为空
// 核心线程和非核心线程都是在这里进行去除
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 进来了里面表示确确实实有线程需要回收
if (compareAndDecrementWorkerCount(c))
// 因为这里也没有任务了
// 将线程删除后返回空
return null;
// 如果没有成功,继续下次循环
continue;
}
try {
// 这里表示线程池的状态检测和线程数量检测都符合,可以获取任务
// 这个timed表示之前是否设置过需要回收线程
Runnable r = timed ?
// 如果需要会设置一个定时获取,如果没有获取到就是null
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 这里会被永久阻塞,也就是为什么核心线程会被留在这里的原因
workQueue.take();
// 如果获取到了任务
if (r != null)
// 直接返回任务
return r;
// 因为如果上面返回了任务,这里就不会执行
// 如果这里设置了超时,就表示阻塞队列没有任务了,可以直接去除
timedOut = true;
// 然后继续自旋
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
}
总结:其实这个getTask主要分为两个部分,关键是对timed的值判断 第一部分:判断线程池状态和判断是否需要减少线程,如果需要减少线程,先减少ctl的值,还没有真正销毁worker,是通过返回的null根据后续逻辑进行销毁 第二部分:获取阻塞队列中的任务进行执行,这里需要注意,去除线程池中的线程是通过timed标记进行判断,如果timed为true表示确实有线程需要被回收,获取阻塞队列的时间为初始化线程池的时间,如果timed为false,表示没有线程需要被回收,获取阻塞队列任务的时间为永久,也就是main线程其实永远不会停下来 timed的值判断:如果核心线程确认回收为true,如果线程池线程数量大于预设值,为true,理解了这个变量,整个流程就很简单了
- 如果从getTask中返回了null,表示需要去除一个线程,因为不满足while循环条件,所以退出循环,进入finally语句
java
public class ThreadPoolExecutor{
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
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 {
// 这个就是关键方法,真正删除线程池中线程的操作
// completedAbruptly这个值如果是正常获取完任务的话都是false
processWorkerExit(w, completedAbruptly);
}
}
}
java
public class ThreadPoolExecutor{
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 判断程序是否正常终止
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 线程池完成任务数+1
completedTaskCount += w.completedTasks;
// 真正的删除线程池中的线程
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
// 下面是保证如果还有任务的话,会将上面移除掉的线程补偿
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
// 进来这里面表示线程池状态为running或shutdown
if (!completedAbruptly) {
// 如果是正常执行完的话,设置最小值
// 如果设置了核心线程也可以回收的话,最小值就是0
// 如果没有设置可以回收,最小值就是我们自己设置的核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果设置了核心线程可回收,但是还有工作线程
if (min == 0 && ! workQueue.isEmpty())
// 那就保留一个线程数量
min = 1;
if (workerCountOf(c) >= min)
return;
}
// 补偿线程
addWorker(null, false);
}
}
}
- 以上就是线程池的完整分析,希望对大家有用,如果有错误,也欢迎在评论区指正!