线程池相关分析可参考 https://my.oschina.net/igooglezm/blog/755508
org.springframework.scheduling.config.TaskNamespaceHandler:注册task命名空间标签解析器
task:annotation-driven ->AnnotationDrivenBeanDefinitionParser
task:executor ->ExecutorBeanDefinitionParser
task:scheduler ->SchedulerBeanDefinitionParser
task:scheduled-tasks ->ScheduledTasksBeanDefinitionParser
spring task sample
<task:annotation-driven/>
<task:executor id="myExecutor"/>
<task:scheduler id="myScheduler"/>
<task:scheduled-tasks>
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
org.springframework.scheduling.config.AnnotationDrivenBeanDefinitionParser
强制单例,不允许有id属性
1.task:annotation-driven
2.处理task:annotation-driven属性
mode
executor
scheduler
exceptionHandler
proxyTargetClass
3.利用AsyncAnnotationBeanPostProcessor处理@Async
annotation @Async 使用包装了的java的ThreadPoolExecutor executor线程池
4.利用ScheduledAnnotationBeanPostProcessor处理@Scheduled
annotation @Scheduled 使用包装了java的ScheduledThreadPoolExecutor scheduler线程池
5.对于executor
优先使用自己的属性executor指定的线程池
见AnnotationDrivenBeanDefinitionParser.parse() Line 74~77
如果没有就在上下文中寻找id为taskExecutor或者类为TaskExecutor的线程池
见AnnotationDrivenBeanDefinitionParser.parse() Line 74~77
如果还没有就使用@Async默认的SimpleAsyncTaskExecutor(Sping自己实现的线程不会重用的线程池)
见AnnotationDrivenBeanDefinitionParser.parse() Line 85
和AsyncAnnotationBeanPostProcessor.setBeanFactory()Line148/AsyncAnnotationAdvisor()Line91
6.对于scheduler
优先使用自己的属性scheduler指定的线程池(定时周期调度器)
见AnnotationDrivenBeanDefinitionParser.parse() Line 97~100
如果没有就在上下文中寻找id为taskScheduler或者类为TaskScheduler的线程池
见AnnotationDrivenBeanDefinitionParser.parse() Line 97~100
如果还没有就使用@Scheduled默认设置的Executors.newSingleThreadScheduledExecutor()
见AnnotationDrivenBeanDefinitionParser.parse() Line 101
和ScheduledAnnotationBeanPostProcessor.finishRegistration() Line225/ScheduledTaskRegistrar.scheduleTasks()Line336~339
org.springframework.scheduling.config.ExecutorBeanDefinitionParser
非强制单例
1.task:executor,对应ThreadPoolTaskExecutor,实际上是包装了的ThreadPoolExecutor
2.处理task:executor属性
keep-alive 对应ThreadPoolExecutor.keepAliveTime
queue-capacity 对应ThreadPoolExecutor.workQueue
pool-size 对应ThreadPoolExecutor.corePoolSize和ThreadPoolExecutor.maximumPoolSize,
若没有指定默认值,默认是1-Integer.MAX
org.springframework.scheduling.config.SchedulerBeanDefinitionParser
非强制单例
1.task:scheduler,对应ThreadPoolTaskScheduler,实际上是包装了的ScheduledThreadPoolExecutor
2.处理task:scheduler属性
pool-size 对应ThreadPoolExecutor.corePoolSize和ThreadPoolExecutor.maximumPoolSize,
若没有指定默认值,默认是1-Integer.MAX
org.springframework.scheduling.config.ScheduledTasksBeanDefinitionParser
强制单例对象,不允许有id属性
1.task:scheduled-tasks,对应ContextLifecycleScheduledTaskRegistrar
2.task:scheduled,对应ScheduledMethodRunnable
3.处理task:scheduled-tasks属性
scheduler,对应ThreadPoolTaskScheduler,实际上包装的ScheduledThreadPoolExecutor
4.处理task:scheduled属性
ref
method
cron
fixed-delay
fixed-rate
trigger
initial-delay
5.对于scheduler
优先使用自己的属性scheduler指定的线程池
如果没有就在上下文中寻找id为taskScheduler的线程池
见ScheduledTasksBeanDefinitionParser.doParse() Line 124~127
如果还没有就是用默认的单线程线程池Executors.newSingleThreadScheduledExecutor();
见ScheduledTaskRegistrar.scheduleTasks() Line 336~339
三种周期任务均是在每次任务执行完成后才会计算该周期任务下一次执行时间点
- FixedDelay任务,在当前任务执行完后再计算下一次开始执行的时间点(需要当前任务执行完成的时间点)
- Cron任务,由对象CronSequenceGenerator来计算下一次开始执行的时间点,也是在当前任务执行完成后再计算下一次的执行时间点(也需要当前任务执行完成的时间点)
- FixedRate任务,可以直接计算下次甚至每次开始执行的时间点,但也是在任务执行完后再计算下一次开始执行的时间点(不需要当前任务执行完成的时间点)
三种周期任务计算下一次执行时间点的依据不全相同
- FixedDelay任务,基于上次执行完时的时间点+delay配置计算下次开始执行的时间点
- Cron任务,基于上次执行完时的时间点+cron配置计算下次开始执行的时间点
- FixedRate任务,基于执行开始的时间点+rate配置计算下一次开始执行的时间点
三种周期任务中任何一个周期任务在等待队列里最多只有一个副本,要么在队列里等待执行,要么在worker线程里正在执行或者等待到时执行
如果任务因为worker线程不够用而被延期(FixedDelay和Cron延期执行只是一种效果,FixRate累积执行也只是一种效果,实际上在任务上一次调度还没有执行完或者还没有被调度,等待队列里是不会存放该周期任务下一次调度的,依据ScheduledThreadPoolExecutor的实现原理,每一个周期任务至多只会有一个副本<该副本要么在执行,要么在等待任务队列里>):
- 对于FixedDelay而言,至多只有一个被耽误的任务,该任务将在获得worker线程时执行.(只要该周期任务存在且已到期或者过期,就会被执行)所以FixedDelay会存在至多一次被耽误的任务,如果执行完该次被耽误的任务,后续空闲worker线程一直够用,其后的同周期下一次调度将会准时;
- 对于Cron而言,至多只有一个被耽误的任务,该任务将在获得worker线程时执行.(只要该周期任务存在且已到期或者过期,就会被执行)所以Cron会存在至多一次被耽误的任务,如果执行完该次被耽误的任务,后续空闲worker线程一直够用,其后的同周期下一次调度将会准时;
- 对于FixedRate而言(实际上也至多只有一个被耽误的任务,该任务将在获得worker线程时执行)但开始执行时间点必须一一累加,直至当前乃至下一次的执行时间点,即中间被耽误的周期任务序列仍会被一一创建、添加、调度和执行. 因为只要该周期任务存在且已到期或者过期,都会被执行,好像“FixedRate积存了很多被耽误的任务在队列里”,然后这些被耽误的任务也最终执行了。但实际上FixedRate也只是最多存了一次被耽误的任务,如果后续空闲worker线程一直够用,其后的同周期下一次调度仍可能已经延误,被延误的任务又被添加到等待队列然后被快速调度执行,到计算的开始执行时间点超过当前时间;(这种算法是一种保护内存的算法,避免同周期任务对象无限创建)
- worker线程执行任务主流程ScheduledThreadPoolExecutor.FutureScheduledTask.run()
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) { //运行任务主体,运行完后<不管成功失败>才会计算下一次执行时间点,进而重新进任务队列
setNextRunTime(); //计算下一次执行时间点
reExecutePeriodic(outerTask); //添加任务至等待队列
}
}
所有还没调度的任务<即任务等待队列workQueue里的对象个数>总数永远不会大于不同周期任务数
- 线程池里正在运行的worker线程个数永远不会大于不同周期任务数
- 同一个周期任务上一次没有执行完,任务等待队列里是不会有该周任务的下一次调度对象的(即如果上一次还没执行,或者没有执行完,后续的同周期任务永远不会执行!因为只有任务执行完才会添加新的任务到队列,任务等待队列里不会存在该周期任务的信息)
- 基于以上原因,不会有两个及两个以上worker线程同时执行一个周期任务的同一次调度
- 基于ScheduledThreadPoolExecutor的实现原理,也不会有两个及两个以上worker线程同时执行同一个周期任务的多次调度
- 线程池的worker线程个数可能设置的很大,超过了不同周期任务数,但其大部分应该是处于空闲状态,即没有运行任务
- 由此可以得出结论,线程池的corePoolSize个数设置超过已知不同周期任务数,意义不大(间接线程池线程复用的原则)
- 如果corePoolSize大于或者等于不同周期任务数,但存在某些任务延期执行或者没有执行,问题原因不会是不同周期任务竞争线程池里的worker线程,而是在于该任务本身出了问题(该任务本身执行时间过长,甚至bolck住了)