一般简单的定时任务需要使用Spring的Scheduled执行比较简单,但是如果不了解原理,可能会入坑。下面简单的从源码出发介绍下;有兴趣可以在标识处打个断点调试下。
Spring的Scheduled内部实现就是将方法体构造成Runnable,根据注解中不同配置构造,在内部调用JUC的ScheduledExecutorService的API去实现,这里有个问题就是ScheduledThreadPoolExecutor的线程数配置几个,如果配置的不对,对时间敏感性的需求是有问题的,直接表现为定时任务没有按指定时间运行;根本原因在于默认配置,内部只有一个线程执行。
解决方式按需求配置多个线程执行,配置方式有两种:
第一种 配置Bean
@Bean("taskScheduler")
public TaskScheduler taskScheduler() {
return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
}
第二种 实现SchedulingConfigurer
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ConcurrentTaskScheduler concurrentTaskScheduler = new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
taskRegistrar.setTaskScheduler(concurrentTaskScheduler);
}
如果两种都存在,根据下面源码分析知道第二种生效
通过EnableScheduling找到SchedulingConfiguration,定位到ScheduledAnnotationBeanPostProcessor
注:配置类 项目里的定时任务配置QuartzConfig, 后面有用
下面代码已经删除无关逻辑, 基于Spring 5.12
@Slf4j
@Configuration
@EnableScheduling
public class QuartzConfig implements SchedulingConfigurer {
//配置方式1
// @Bean("taskScheduler") //name=taskScheduler 必须?
// public TaskScheduler taskScheduler() {
// return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
// }
/**
* 配置方式2
* @param taskRegistrar the registrar to be configured.
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ConcurrentTaskScheduler concurrentTaskScheduler = new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
taskRegistrar.setTaskScheduler(concurrentTaskScheduler);
//注释这个原理是一样的
//taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
}
@Scheduled(fixedRate = 1000 * 20)
public void scheduledTaskA20() {
log.error("任务A每20秒执行一次");
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
log.error("InterruptedException A");
}
}
@Scheduled(fixedRate = 1000 * 10)
public void scheduledTaskB10() {
log.error("任务B每10秒执行一次");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
log.error("InterruptedException B");
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
Class> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
//1.
Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup>) method -> {
Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
});
if (!annotatedMethods.isEmpty()) {
annotatedMethods.forEach((method, scheduledMethods) ->
//2.
schedudMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
}
}
return bean;
}
1、找到配置类里所有Scheduled注解的方法
2、调用processScheduled将方法封装成对应的定时任务类型
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
//3.
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
Set tasks = new LinkedHashSet<>(4);
//4.1 CronTask
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
//...
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
//4.2 FixedDelayTask
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
//4.3 FixedDelayTask
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
fixedDelay = parseDelayAsLong(fixedDelayString);
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
//4.4 FixedRateTask
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
//4.5 FixedRateTask
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
fixedRate = parseDelayAsLong(fixedRateString);
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
//5.
synchronized (this.scheduledTasks) {
Set regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
}
3、将配置类里的逻辑转换成Runnable(ScheduledMethodRunnable)
4.x、分析注解对象,转换成对应的定时任务,如下图
5、处理完后交给ScheduledTaskRegistrar
Spring的onApplicationEvent触发finishRegistration
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
Map beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
//6.
configurer.configureTasks(this.registrar);
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
try {
//7.
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
}
}
catch (NoSuchBeanDefinitionException ex) {
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
}
}
}
//9.触发定时任务生效
this.registrar.afterPropertiesSet();
}
private T resolveSchedulerBean(BeanFactory beanFactory, Class schedulerType, boolean byName) {
if (byName) {
T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(
DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
}
return scheduler;
}
//8.
else if (beanFactory instanceof AutowireCapableBeanFactory) {
NamedBeanHolder holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
finishRegistration主要负责配置TaskScheduler的;
6. 如果实现SchedulingConfigurer接口的方式配置TaskScheduler,则在此处生效
7.如果通过配置TaskScheduler的Bean的形式,会在8.处找到配置的Bean,进而成效
如果没有配置用什么?见ScheduledTaskRegistrar的分析
通过ScheduledAnnotationBeanPostProcessor的processScheduled方法,ScheduledTaskRegistrar会得到各种任务的集合,再由ScheduledAnnotationBeanPostProcessor触发定时任务的执行;见9.处注释。
以配置类里的固定频率执行的任务来看主要逻辑,其他类型定时任务也是类似的。
public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
// 上面两种配置方式,第一次到这里taskScheduler是null
if (this.taskScheduler != null) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
//13.
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
}
else {
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
}
}
else {
//10.
addFixedRateTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
10、将定时任务添加到fixedRateTasks集合
13、将定时任务交给ScheduledExecutorService执行
11、从这里可以看到,如果没有配置TaskScheduler,默认的是单线程的ConcurrentTaskScheduler
12、以fixedRateTasks来说,经过finishRegistration()配置好TaskScheduler后,可以触发定时任务的执行了
内部还是执行scheduleFixedRateTask()的逻辑。
到这里所有的逻辑基本走完了,下面简单说一下定时任务的执行容器ConcurrentTaskScheduler。
内部有一个JUC 的ScheduledExecutorService scheduledExecutor,根据参数配置;
对应各种类型的任务使用scheduledExecutor的API:
ScheduledFuture> schedule(Runnable command, ong delay, TimeUnit unit);
ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
ScheduledFuture> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay, TimeUnit unit);
默认配置和配置线程之后效果对比:
2020-01-12 19:56:51.898 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:57:01.904 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:57:11.905 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 19:57:31.911 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:57:42.166 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:57:52.170 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 19:58:12.173 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:58:22.179 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 19:58:32.184 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 20:13:37.756 ERROR 35810 [taskScheduler-3] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:13:47.752 ERROR 35810 [taskScheduler-2] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 20:13:47.764 ERROR 35810 [taskScheduler-4] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:13:57.766 ERROR 35810 [taskScheduler-5] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:14:07.758 ERROR 35810 [taskScheduler-6] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 20:14:07.769 ERROR 35810 [taskScheduler-7] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:14:17.774 ERROR 35810 [taskScheduler-1] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:14:27.761 ERROR 35810 [taskScheduler-4] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 20:14:27.780 ERROR 35810 [taskScheduler-9] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:14:37.786 ERROR 35810 [taskScheduler-5] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
2020-01-12 20:14:47.766 ERROR 35810 [taskScheduler-8] c.h.s.m.c.QuartzConfig : 任务A每20秒执行一次
2020-01-12 20:14:47.790 ERROR 35810 [askScheduler-11] c.h.s.m.c.QuartzConfig : 任务B每10秒执行一次
1、不支持动态修改定时任务的执行频率;
2、不直接支持分布式部署;
3、如上分析,面对不对变化的需求,线程池大小配置是个问题;
如何解决,用Quartz吧。