ScheduledThreadPoolExecutor源码学习

一、首先先看一下线程池的创建:

image2019-4-19%2014%3A38%3A46.png?version=1&modificationDate=1555671478000&api=v2

corePoolSize:线程池核心线程数量

maximumPoolSize:线程池最大线程数量

keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间

unit:存活时间的单位

workQueue:存放任务的队列

handler:超出线程范围和队列容量的任务的处理程序

二、知道这几个参数之后,看一下线程池的原理:

提交一个任务到线程池中,线程池的处理流程如下:

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

image2019-4-19%2014%3A45%3A35.png?version=1&modificationDate=1555671478000&api=v2

拒绝策略有3种:如下

image2019-4-19%2015%3A32%3A10.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2015%3A32%3A27.png?version=1&modificationDate=1555671478000&api=v2

三、在分析ScheduledThreadPoolExecutor之前,我们先看看ThreadPoolExecutor的源码,然后在看ScheduledThreadPoolExecutor,对比分析ScheduledThreadPoolExecutor产生的原因和使用场景。

1.ThreadPoolExecutor 的源码

image2019-4-19%2014%3A48%3A55.png?version=1&modificationDate=1555671478000&api=v2

由此可以看到ThreadPoolExecutor本质上就是个Executor,所以他的的核心是execute方法。我们来看一下execute方法:

image2019-4-19%2014%3A51%3A40.png?version=1&modificationDate=1555671478000&api=v2

在具体看这个方法之前,可以看看这个方法的注释(自己翻译吧,英语太菜,只会直译):

1. 如果运行线程数小于核心线程数,则尝试用给定的命令作为第一个任务启动一个新线程。调用addWorker会原子性地检查runState和workerCount,以此来防止错误警报,因为错误警报会在不应该添加线程的时候添加线程。

2. 如果一个任务可以成功添加到队列,那么我们仍然需要再次检查是否应该添加一个线程(当自上次检查以来已有的线程已经死亡),或者在进入这个方法后关闭线程池。因此,我们重新检查状态,如果必要的话,当线程池关闭时回滚队列;当没有线程的时候,则启动一个新线程。

3.如果任务无法添加到队列,则尝试添加新线程。如果它失败了,我们知道我们被关闭或饱和,所以拒绝任务。

这个方法的注释,就是前面写的,线程池的处理流程。

然后还可以看看这个类的这2段注释,更好的理解这个流程:

image2019-4-19%2015%3A27%3A28.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2015%3A31%3A15.png?version=1&modificationDate=1555671478000&api=v2

现在来分析下execute方法:

首先ctl的参数,一眼看上去不知道是什么,然后我们先看看这个类关于这个参数的定义:

image2019-4-19%2015%3A38%3A39.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2015%3A41%3A7.png?version=1&modificationDate=1555671478000&api=v2

再跟一下这个ctl:

image2019-4-19%2015%3A15%3A33.png?version=1&modificationDate=1555671478000&api=v2

所以ctl.get()就是获取主池控制状态ctl。

当正在运行的线程数小于核心线程数,看一下addWorker(command,true)方法:

image2019-4-19%2015%3A50%3A38.png?version=1&modificationDate=1555671478000&api=v2

 

image2019-4-19%2015%3A51%3A3.png?version=1&modificationDate=1555671478000&api=v2

关于这个方法,先看一下这段注释:

image2019-4-19%2016%3A2%3A0.png?version=1&modificationDate=1555671478000&api=v2

然后可以在前面看看runState的状态,基本上这个方法就能自己看明白了。其中需要注意的是:

image2019-4-19%2016%3A11%3A16.png?version=1&modificationDate=1555671478000&api=v2

这个线程是禁止打断的执行运行。

还有就是最后finally做了个判断,如果workStarted为false,会从线程池中移除该线程。

然后再看一下Worker:

image2019-4-19%2016%3A31%3A3.png?version=1&modificationDate=1555671478000&api=v2

继承了AQS类,可以方便的实现工作线程的中止操作;

实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;

当前提交的任务firstTask作为参数传入Worker的构造方法;

所以我们应该看一下他的run方法:

image2019-4-19%2016%3A31%3A51.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2016%3A39%3A56.png?version=1&modificationDate=1555671478000&api=v2

这个runWorker方法(上面的图方法没截全),主要是以下几步:

unlock释放锁,表示可中断;

firstTask不为null或者从线程池中获取的线程不为null,线程加锁,检查线程池状态,看是否可中断,可以的话就进行中断;

执行beforeExecute方法,run方法,afterExecute方法;

释放锁。

然后在看一下getTask方法:

image2019-4-19%2016%3A49%3A28.png?version=1&modificationDate=1555671478000&api=v2

这个方法看一下注释,基本上就没啥了。

image2019-4-19%2016%3A49%3A59.png?version=1&modificationDate=1555671478000&api=v2

看完这些之后,再返回到execute方法:

image2019-4-19%2016%3A25%3A55.png?version=1&modificationDate=1555671478000&api=v2

关于ThreadPoolExecutor的execute方法就分析完了。
execute是任务的执行,再看一下任务的提交,submit方法:

image2019-4-19%2016%3A56%3A33.png?version=1&modificationDate=1555671478000&api=v2

ThreadPoolExecutor并没有submit方法,而他的父类AbstractExecutorService实现了ExecutorService的submit方法。

image2019-4-19%2017%3A9%3A28.png?version=1&modificationDate=1555671479000&api=v2

这个execute方法已经分析过了,现在就看看newTaskFor方法:

image2019-4-19%2017%3A12%3A14.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2017%3A12%3A53.png?version=1&modificationDate=1555671478000&api=v2

image2019-4-19%2017%3A14%3A42.png?version=1&modificationDate=1555671478000&api=v2

既然有这么多状态,肯定有对状态的获取方法:

看一下get()方法:

image2019-4-19%2017%3A22%3A37.png?version=1&modificationDate=1555671479000&api=v2

内部通过awaitDone方法对主线程进行阻塞:

image2019-4-19%2017%3A25%3A26.png?version=1&modificationDate=1555671479000&api=v2

如果主线程被中断,则抛出中断异常;
判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;
如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出cpu资源,等待state变成NORMAL;
通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;
最终通过LockSupport的park或parkNanos挂起线程;
 

看完这个之后,在看一下FutureTask的类图关系:

image2019-4-19%2017%3A17%3A32.png?version=1&modificationDate=1555671478000&api=v2

然后看一下FutureTask的run方法:

image2019-4-19%2017%3A18%3A46.png?version=1&modificationDate=1555671479000&api=v2

 

这样的话,ThreadPoolExecutor 的源码部分就分析差不多了。

然后如果想给这个类添加额外的功能,可以看看这个示例:

image2019-4-19%2017%3A32%3A28.png?version=1&modificationDate=1555671479000&api=v2

image2019-4-19%2017%3A32%3A47.png?version=1&modificationDate=1555671479000&api=v2

2.然后看一下ScheduledThreadPoolExecutor:

这个类有这样一段注释:

image2019-4-19%2017%3A38%3A34.png?version=1&modificationDate=1555671479000&api=v2

我们还是跟ThreadPoolExecutor一样的方式,研究一下这个类:

先看execute方法:

image2019-4-19%2017%3A41%3A45.png?version=1&modificationDate=1555671479000&api=v2

image2019-4-19%2017%3A42%3A13.png?version=1&modificationDate=1555671479000&api=v2

调用delayedExecute(t)延迟任务的执行:

image2019-4-19%2017%3A49%3A29.png?version=1&modificationDate=1555671479000&api=v2

刚加进去的线程,肯定是走else的,看一下ensurePrestart方法:

image2019-4-19%2017%3A51%3A7.png?version=1&modificationDate=1555671479000&api=v2

走到这里就可以看出来,跟ThreadPoolExecutor的addWorker是一样的了。

所以我们需要好好看看这个部分:

image2019-4-19%2017%3A54%3A4.png?version=1&modificationDate=1555671479000&api=v2

先看一下triggerTime方法:获取下一次具体执行时间

image2019-4-19%2017%3A54%3A42.png?version=1&modificationDate=1555671479000&api=v2

然后看一下ScheduledFutureTask:

image2019-4-19%2018%3A2%3A7.png?version=1&modificationDate=1555671479000&api=v2

这个FutureTask是不是很熟悉,这个在前面ThreadPoolExecutor的时候分析过,然后ScheduledFutureTask重新了写了run方法:

image2019-4-19%2018%3A20%3A43.png?version=1&modificationDate=1555671479000&api=v2

image2019-4-19%2018%3A6%3A28.png?version=1&modificationDate=1555671479000&api=v2

image2019-4-19%2018%3A21%3A37.png?version=1&modificationDate=1555671479000&api=v2

看完这个之后,再看一下ScheduledFutureTask的构造方法:

image2019-4-19%2018%3A34%3A39.png?version=1&modificationDate=1555671479000&api=v2

time:下次任务执行时的时间;
period:执行周期;
sequenceNumber:保存任务被添加到ScheduledThreadPoolExecutor中的序号。

再看一下这个类:

image2019-4-19%2018%3A22%3A47.png?version=1&modificationDate=1555671479000&api=v2

这个就整体串起来了。

定时任务,一般会调用ScheduledThreadPoolExecutor这2个方法:

image2019-4-19%2018%3A24%3A45.png?version=1&modificationDate=1555671479000&api=v2

scheduleAtFixedRate方法
该方法设置了执行周期,下一次执行时间相当于是上一次的执行时间加上period,它是采用已固定的频率来执行任务:

image2019-4-19%2018%3A26%3A39.png?version=1&modificationDate=1555671479000&api=v2

scheduleWithFixedDelay方法
该方法设置了执行周期,与scheduleAtFixedRate方法不同的是,下一次执行时间是上一次任务执行完的系统时间加上period,因而具体执行时间不是固定的,但周期是固定的,是采用相对固定的延迟来执行任务:

image2019-4-19%2018%3A27%3A43.png?version=1&modificationDate=1555671479000&api=v2

这里的unit.toNanos(-delay)),再看一下setNextRunTime()方法,就能理解了,这里把周期设置为负数来表示是相对固定的延迟执行。

再看ScheduledThreadPoolExecutor的submit方法:

image2019-4-19%2018%3A37%3A55.png?version=1&modificationDate=1555671479000&api=v2

也是走到schedule方法,剩下的就自己看看吧。

(shutdown的部分没有写,可以自己看看)

转载于:https://my.oschina.net/u/3944601/blog/3053765

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值