一、首先先看一下线程池的创建:
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序
二、知道这几个参数之后,看一下线程池的原理:
提交一个任务到线程池中,线程池的处理流程如下:
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
拒绝策略有3种:如下
三、在分析ScheduledThreadPoolExecutor之前,我们先看看ThreadPoolExecutor的源码,然后在看ScheduledThreadPoolExecutor,对比分析ScheduledThreadPoolExecutor产生的原因和使用场景。
1.ThreadPoolExecutor 的源码
由此可以看到ThreadPoolExecutor本质上就是个Executor,所以他的的核心是execute方法。我们来看一下execute方法:
在具体看这个方法之前,可以看看这个方法的注释(自己翻译吧,英语太菜,只会直译):
1. 如果运行线程数小于核心线程数,则尝试用给定的命令作为第一个任务启动一个新线程。调用addWorker会原子性地检查runState和workerCount,以此来防止错误警报,因为错误警报会在不应该添加线程的时候添加线程。
2. 如果一个任务可以成功添加到队列,那么我们仍然需要再次检查是否应该添加一个线程(当自上次检查以来已有的线程已经死亡),或者在进入这个方法后关闭线程池。因此,我们重新检查状态,如果必要的话,当线程池关闭时回滚队列;当没有线程的时候,则启动一个新线程。
3.如果任务无法添加到队列,则尝试添加新线程。如果它失败了,我们知道我们被关闭或饱和,所以拒绝任务。
这个方法的注释,就是前面写的,线程池的处理流程。
然后还可以看看这个类的这2段注释,更好的理解这个流程:
现在来分析下execute方法:
首先ctl的参数,一眼看上去不知道是什么,然后我们先看看这个类关于这个参数的定义:
再跟一下这个ctl:
所以ctl.get()就是获取主池控制状态ctl。
当正在运行的线程数小于核心线程数,看一下addWorker(command,true)方法:
关于这个方法,先看一下这段注释:
然后可以在前面看看runState的状态,基本上这个方法就能自己看明白了。其中需要注意的是:
这个线程是禁止打断的执行运行。
还有就是最后finally做了个判断,如果workStarted为false,会从线程池中移除该线程。
然后再看一下Worker:
继承了AQS类,可以方便的实现工作线程的中止操作;
实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;
当前提交的任务firstTask作为参数传入Worker的构造方法;
所以我们应该看一下他的run方法:
这个runWorker方法(上面的图方法没截全),主要是以下几步:
unlock释放锁,表示可中断;
firstTask不为null或者从线程池中获取的线程不为null,线程加锁,检查线程池状态,看是否可中断,可以的话就进行中断;
执行beforeExecute方法,run方法,afterExecute方法;
释放锁。
然后在看一下getTask方法:
这个方法看一下注释,基本上就没啥了。
看完这些之后,再返回到execute方法:
关于ThreadPoolExecutor的execute方法就分析完了。
execute是任务的执行,再看一下任务的提交,submit方法:
ThreadPoolExecutor并没有submit方法,而他的父类AbstractExecutorService实现了ExecutorService的submit方法。
这个execute方法已经分析过了,现在就看看newTaskFor方法:
既然有这么多状态,肯定有对状态的获取方法:
看一下get()方法:
内部通过awaitDone方法对主线程进行阻塞:
如果主线程被中断,则抛出中断异常;
判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;
如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出cpu资源,等待state变成NORMAL;
通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;
最终通过LockSupport的park或parkNanos挂起线程;
看完这个之后,在看一下FutureTask的类图关系:
然后看一下FutureTask的run方法:
这样的话,ThreadPoolExecutor 的源码部分就分析差不多了。
然后如果想给这个类添加额外的功能,可以看看这个示例:
2.然后看一下ScheduledThreadPoolExecutor:
这个类有这样一段注释:
我们还是跟ThreadPoolExecutor一样的方式,研究一下这个类:
先看execute方法:
调用delayedExecute(t)延迟任务的执行:
刚加进去的线程,肯定是走else的,看一下ensurePrestart方法:
走到这里就可以看出来,跟ThreadPoolExecutor的addWorker是一样的了。
所以我们需要好好看看这个部分:
先看一下triggerTime方法:获取下一次具体执行时间
然后看一下ScheduledFutureTask:
这个FutureTask是不是很熟悉,这个在前面ThreadPoolExecutor的时候分析过,然后ScheduledFutureTask重新了写了run方法:
看完这个之后,再看一下ScheduledFutureTask的构造方法:
time:下次任务执行时的时间;
period:执行周期;
sequenceNumber:保存任务被添加到ScheduledThreadPoolExecutor中的序号。
再看一下这个类:
这个就整体串起来了。
定时任务,一般会调用ScheduledThreadPoolExecutor这2个方法:
scheduleAtFixedRate方法
该方法设置了执行周期,下一次执行时间相当于是上一次的执行时间加上period,它是采用已固定的频率来执行任务:
scheduleWithFixedDelay方法
该方法设置了执行周期,与scheduleAtFixedRate方法不同的是,下一次执行时间是上一次任务执行完的系统时间加上period,因而具体执行时间不是固定的,但周期是固定的,是采用相对固定的延迟来执行任务:
这里的unit.toNanos(-delay)),再看一下setNextRunTime()方法,就能理解了,这里把周期设置为负数来表示是相对固定的延迟执行。
再看ScheduledThreadPoolExecutor的submit方法:
也是走到schedule方法,剩下的就自己看看吧。
(shutdown的部分没有写,可以自己看看)