定时/延时任务-万字解析Spring定时任务原理

文章目录

  • 1. 概要
  • 2. @EnableScheduling 注解
  • 3. @Scheduled 注解
  • 4. postProcessAfterInitialization 解析
    • 4.1 createRunnable
  • 5. 任务 Task 和子类
  • 6. ScheduledTaskRegistrar
    • 6.1 添加任务的逻辑
    • 6.2 调度器初始化
    • 6.3 调用时机
  • 7. taskScheduler 类型
    • 7.1 ConcurrentTaskScheduler
    • 7.2 ThreadPoolTaskScheduler
  • 8. 小结

1. 概要

上一篇文章:定时/延时任务-Spring定时任务的两种实现方式。这篇文章就来看下 Spring 中 @Scheduled 和接口方式的定时任务是如何实现的。

2. @EnableScheduling 注解

Spring 中如果需要使用定时任务,就需要引入 @EnableScheduling,我们看下这个注解是怎么定义的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}

在这个注解中关键就是:@Import(SchedulingConfiguration.class),这里面通过 Import 引入了 SchedulingConfiguration 配置类。Import 是 Spring 中定义的一种引入配置类的方式,通过 Import 注解可以把对应的类交给 Spring 管理,达到动态开关配置的目的。然后我们再来看下引入的这个注解配置类。

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}}

可以看到这个配置类代码比较简单,就是注册了一个 ScheduledAnnotationBeanPostProcessor 后置处理器。后置处理器是 Spring 中用于在 bean 初始化之后调用来处理 bean 的方法。对于 @Scheduled 和 @Schedules 注解解析的核心逻辑就在 postProcessAfterInitialization 中。但是在这之前,我们看下 @Scheduled 注解的属性。

3. @Scheduled 注解

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {/*** 是否禁用 cron 表达式,如果设置成 '-' 就表示不使用 cron 表达式*/String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;/*** cron 表达式* @return*/String cron() default "";/*** 时区* @return*/String zone() default "";/*** 固定延时任务的延时时间* @return*/long fixedDelay() default -1;/*** 固定延时任务的延时时间字符串,可动态配置* @return*/String fixedDelayString() default "";/*** 固定速率任务的延时时间* @return*/long fixedRate() default -1;/*** 固定速率任务的延时时间字符串,可动态配置* @return*/String fixedRateString() default "";/*** 第一次执行任务之前延迟多少秒* @return*/long initialDelay() default -1;/*** 第一次执行任务之前延迟多少秒,字符串,可动态配置* @return*/String initialDelayString() default "";/*** 时间单位,默认是毫秒* @return*/TimeUnit timeUnit() default TimeUnit.MILLISECONDS;}

@Scheduled 定义了几种定时任务的实现方式

  1. cron 表达式任务
  2. 固定延时任务 fixedDelay
  3. 固定速率任务 fixedRate

然后我们再看下 postProcessAfterInitialization 是如何处理上面这几种方法的。


4. postProcessAfterInitialization 解析

/*** 初始化之后回回调后置处理器处理定时任务* @param bean the new bean instance* @param beanName the name of the bean* @return*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||bean instanceof ScheduledExecutorService) {// 注册的 bean 是上面类型的就不处理// Ignore AOP infrastructure such as scoped proxies.return bean;}// 获取实际要处理的 bean 的类型Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);// 1.如果这个目标类还没有被处理过// 2.这个类能不能被 Scheduled 或者 Schedules 注解处理(如果这个类是以 java. 开头或者是 Ordered 类,就不可以)if (!this.nonAnnotatedClasses.contains(targetClass) &&AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {// 从目标类中获取有 Scheduled 或者 Schedules 注解的方法Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);});// 如果找不到方法if (annotatedMethods.isEmpty()) {// 加入 nonAnnotatedClasses 集合中this.nonAnnotatedClasses.add(targetClass);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + targetClass);}}else {// 找到了包含特定注解的方法annotatedMethods.forEach((method, scheduledAnnotations) ->// 遍历来处理所有的方法scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isTraceEnabled()) {logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}}return bean;
}

当 ScheduledAnnotationBeanPostProcessor 初始化完成之后,调用 postProcessAfterInitialization 来处理相关的注解,下面来看下具体逻辑。

if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||bean instanceof ScheduledExecutorService) {// 注册的 bean 是上面类型的就不处理// Ignore AOP infrastructure such as scoped proxies.return bean;
}

如果 bean 的类型是 上面三个类型的,就不处理。

// 获取实际要处理的 bean 的类型
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
// 1.如果这个目标类还没有被处理过
// 2.这个类能不能被 Scheduled 或者 Schedules 注解处理(如果这个类是以 java. 开头或者是 Ordered 类,就不可以)
if (!this.nonAnnotatedClasses.contains(targetClass) &&AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {// 从目标类中获取有 Scheduled 或者 Schedules 注解的方法Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);});// 如果找不到方法if (annotatedMethods.isEmpty()) {// 加入 nonAnnotatedClasses 集合中this.nonAnnotatedClasses.add(targetClass);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + targetClass);}}else {// 找到了包含特定注解的方法annotatedMethods.forEach((method, scheduledAnnotations) ->// 遍历来处理所有的方法scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isTraceEnabled()) {logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}
}
return bean;

然后获取下 bean 的类型,并且判断下这个类型能不能被 Scheduled 或者 Schedules 注解处理,如果这个类是以 java. 开头或者是 Ordered 类,就不可以。最后遍历这些标记了上面两个注解的方法,一个一个处理,处理的逻辑是 processScheduled,看下 processScheduled 的逻辑

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {// 根据调用的对象和调用的方法创建一个任务Runnable runnable = createRunnable(bean, method);boolean processedSchedule = false;String errorMessage ="Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";Set<ScheduledTask> tasks = new LinkedHashSet<>(4);...
}

这个方法中首先会创建一个任务,然后设置几个属性。

// 把 @Scheduled 注解的 initialDelay 属性转化成毫秒,initialDelay 是指延时多少秒进行第一次执行
long initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit());
// @Scheduled 注解的 initialDelayString 属性,作用和上面的 initialDelay 作用一样
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {// 如果指定了 initialDelayString,那么就不能指定 initialDelay 了// 同时指定 initialDelay 会报错Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");if (this.embeddedValueResolver != null) {// 因为 @Scheduled 注解的几个 String 类型的值都可以通过配置文件引入,也就是 ${} 的方式// 这个方法就是去解析动态配置值的initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);}// 如果配置了if (StringUtils.hasLength(initialDelayString)) {try {// 转换成毫秒单位initialDelay = convertToMillis(initialDelayString, scheduled.timeUnit());}catch (RuntimeException ex) {throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");}}
}

上面就是解析 @Scheduled 中 initialDelay 和 initialDelayString 的逻辑。

  1. 首先把 initialDelay 属性转化成毫秒
  2. 然后再解析 initialDelayString,需要注意的是 initialDelay 和 initialDelayString 只能选一个,如果两个都填就会报错
  3. this.embeddedValueResolver.resolveStringValue 是解析动态配置的逻辑,因为 initialDelayString 可以使用 ${} 动态配置
  4. 最后都转化成 initialDelay 毫秒

上面解析的 initialDelay 和 initialDelayString 表示延时多少 ms 再执行第一次任务。下面再来看下 cron 表达式的解析,这是第一种定时任务的配置方式。

// 获取 cron 表达式
String cron = scheduled.cron();
// 如果设置了 cron 表达式
if (StringUtils.hasText(cron)) {// 获取指定的时区String zone = scheduled.zone();// 同时也是去解析 cron 表达式和时区的动态值if (this.embeddedValueResolver != null) {cron = this.embeddedValueResolver.resolveStringValue(cron);zone = this.embeddedValueResolver.resolveStringValue(zone);}// 如果设置了 cron 表达式if (StringUtils.hasLength(cron)) {// 如果设置了 cron 表达式,那么就不能设置 initialDelay 了Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");// 设置下标记,表示已经设置 cron 表达式了processedSchedule = true;// 如果 cron 表达式没有被设置成 '-' 了,就代表用 cron 去触发if (!Scheduled.CRON_DISABLED.equals(cron)) {// 创建 cron 触发器CronTrigger trigger;if (StringUtils.hasText(zone)) {// 设置时区和 cron 表达式trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));}else {// 不需要设置时区,设置 cron 表达式trigger = new CronTrigger(cron);}// 将创建的 ScheduledTask 加入任务集合中// 使用 ScheduledTaskRegistrar 创建一个 ScheduledTask,同时需要传入触发器,这个触发器里面需要传入 cron 表达式和时区tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));}}
}
  1. 解析 cron 表达式,先获取下时区
  2. 然后解析 cron 表达式和时区的动态值
  3. 如果设置了 cron 表达式,那么就不能设置 initialDelay 了,需要依靠 cron 表达式来执行定时任务
  4. processedSchedule 标记位是代表有没有设置了定时任务的调度方式,这里如果使用 cron 表达式来调度任务,processedSchedule 就会设置为 true
  5. 检查下 cron 有没有被设置成 -,这个标记代表禁用 cron 表达式,如果没有设置为 -,就通过 cron 和时区创建一个 CronTrigger,这是 cron 触发器,在这个触发器里面可以获取表达式和时区等信息,同时也可以获取 cron 下一次执行的时间
  6. 新建一个 ScheduledTask,把这个任务添加到任务集合中,这个是任务的统一包装,里面可以对 CronTask、FixedDelayTask、FixedRateTask 进行包装

上面就是解析 cron 表达式的逻辑,下面继续看解析@Scheduled 注解的 fixedDelay 字段逻辑,这个字段就是固定延时任务。

// 下面检查 @Scheduled 注解的 fixedDelay 字段,这个字段就是固定延时任务
long fixedDelay = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit());
if (fixedDelay >= 0) {// 如果前面 cron 已经设置了,那么这里就不能设置 fixedDelay 了Assert.isTrue(!processedSchedule, errorMessage);// 设置标记processedSchedule = true;// 同样添加任务,不过这里是创建一个 FixedDelayTask,表示固定延时的任务tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
// 看下有没有设置 fixedDelayString
String fixedDelayString = scheduled.fixedDelayString();
// 如果设置 fixedDelayString 了
if (StringUtils.hasText(fixedDelayString)) {if (this.embeddedValueResolver != null) {// 解析动态字符串fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);}if (StringUtils.hasLength(fixedDelayString)) {// 如果前面没有设置 cron 和 fixDelayAssert.isTrue(!processedSchedule, errorMessage);// 设置标记,表示已解析processedSchedule = true;try {// 转化成毫秒fixedDelay = convertToMillis(fixedDelayString, scheduled.timeUnit());}catch (RuntimeException ex) {throw new IllegalArgumentException("Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");}// 同样添加任务,不过这里是创建一个 FixedDelayTask,表示固定延时的任务tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));}
}
  1. 首先检测 @Scheduled 注解的 fixedDelay,转化成毫秒
    • 判断下 processedSchedule 字段,如果前面已经设置 cron了,那么这里就会抛出异常,也就是说如果设置了 cron 表达式的调度方式,这里就不能设置 fixedDelay 了
    • 创建一个 ScheduledTask 添加到集合里面,这个任务包装类包装的是 FixedDelayTask,里面设置了初始延时时间和固定速率
  2. 然后继续检测 @Scheduled 注解的 fixedDelayString
    • 解析动态字符串
    • 判断下 processedSchedule 字段,如果前面已经设置 cron 或者设置了 fixedDelay 了,那么这里就会抛出异常,也就是说如果设置了 cron 表达式的调度方式或者设置了 fixedDelay 字段,这里就不能设置 fixedDelayString 了
    • 创建一个 ScheduledTask 添加到集合里面,这个任务包装类包装的是 FixedDelayTask,里面设置了初始延时时间和固定速率

上面就是解析固定速率的逻辑,下面继续看解析@Scheduled 注解的 fixedRate 字段逻辑,这个字段就是固定速率任务。

// 下面检查 @Scheduled 注解的 fixedRate 字段,这个字段就是固定速率任务
long fixedRate = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit());
if (fixedRate >= 0) {// 如果设置了 fixedRate,同时前面几种方式都没有设置Assert.isTrue(!processedSchedule, errorMessage);// 设置标记processedSchedule = true;// 同样添加任务,不过这里是创建一个 FixedRateTask,表示固定延时的任务tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
// 继续检测 fixedRateString
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {if (this.embeddedValueResolver != null) {// 解析动态表达式fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);}if (StringUtils.hasLength(fixedRateString)) {// 如果前面几种方式已经设置过了,这里就抛出异常Assert.isTrue(!processedSchedule, errorMessage);// 设置标记位processedSchedule = true;try {// 把 fixedRateString 转化成毫秒fixedRate = convertToMillis(fixedRateString, scheduled.timeUnit());}catch (RuntimeException ex) {throw new IllegalArgumentException("Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");}// 同样添加任务,不过这里是创建一个 FixedRateTask,表示固定延时的任务tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));}
}
  1. 首先检测 @Scheduled 注解的 fixedRate,转化成毫秒
    • 判断下 processedSchedule 字段,如果前面已经设置 cron了,或者已经设置固定延时的方式了,那么这里就会抛出异常
    • 创建一个 ScheduledTask 添加到集合里面,这个任务包装类包装的是 FixedRateTask,里面设置了初始延时时间和固定速率
  2. 然后继续检测 @Scheduled 注解的 fixedRateString
    • 解析动态字符串
    • 判断下 processedSchedule 字段,如果前面已经设置 cron 或者设置了固定延时或者 fixedRate 字段了,那么这里就会抛出异常fixedRate 字段,这里就不能设置 fixedRateString了
    • 创建一个 ScheduledTask 添加到集合里面,这个任务包装类包装的是 FixedRateTask,里面设置了初始延时时间和固定速率

最后把定时任务注册到 scheduledTasks 中,这个属性是一个 map 任务集合,表示 bean -> Set<Task> 的映射。

private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);

到这里任务就已经被解析完并添加到 scheduledTasks 集合中了,当 bean 注销的时候会从这个集合里面拿出任务来一个一个取消,这个后面再看。

4.1 createRunnable

这个方法一开始就创建了一个任务,我们这里就简单看下创建任务的逻辑。

/*** 根据传入的对象和方法来创建一个任务* @param target* @param method* @return*/
protected Runnable createRunnable(Object target, Method method) {// 如果方法设置了参数,就抛出异常Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");// 选择注解可调用的的同名方法,这个方法不能是 private 或者是 static,又或者是代理类型的Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());// 创建 ScheduledMethodRunnablereturn new ScheduledMethodRunnable(target, invocableMethod);
}

其实逻辑不难,就是从对象里面获取同名方法,这些方法不能是 private 或者是 static,同时这个对象的类不能是代理类。

5. 任务 Task 和子类

上面就是 postProcessAfterInitialization 的逻辑,但是有一些方法我们还没有细看,比如 this.registrar.scheduleCronTask,而这个方法只是把任务添加到集合中,那么这些方法是在哪被调用的呢?既然不是在 ScheduledAnnotationBeanPostProcessor 调度的,是在哪调度的呢?同时在上一篇文章通过接口动态设置 cron 表达式的时候我们不是通过 addTriggerTask 添加了定时任务吗,那么这个方法是在哪里设置任务的, 带着这些问题,下面我们先看下要调度的任务的结构。
不管是上面添加的 CronTask、FixedDelayTask 和 FixedRateTask,都是子类的实现,但是这些类的顶层结构式怎么样的呢?
在这里插入图片描述

  1. 首先顶层结构是 Task
  2. 下面实现类是 TriggerTask 和 IntervalTask
  3. 最后就是 CronTask、FixedDelayTask 和 FixedRateTask,这三个就是我们设置的 cron、固定延时和固定速率任务

下面来看下里面的源码结构,首先是 Task:

public class Task {private final Runnable runnable;public Task(Runnable runnable) {Assert.notNull(runnable, "Runnable must not be null");this.runnable = runnable;}public Runnable getRunnable() {return this.runnable;}@Overridepublic String toString() {return this.runnable.toString();}}

顶层 Task 里面封装了具体的任务 Runnable

public class TriggerTask extends Task {private final Trigger trigger;public TriggerTask(Runnable runnable, Trigger trigger) {super(runnable);Assert.notNull(trigger, "Trigger must not be null");this.trigger = trigger;}public Trigger getTrigger() {return this.trigger;}}public class IntervalTask extends Task {private final long interval;private final long initialDelay;public IntervalTask(Runnable runnable, long interval, long initialDelay) {super(runnable);this.interval = interval;this.initialDelay = initialDelay;}public IntervalTask(Runnable runnable, long interval) {this(runnable, interval, 0);}public long getInterval() {return this.interval;}public long getInitialDelay() {return this.initialDelay;}}
  • TriggerTask 触发器任务是对 cron 任务的封装,里面创建了一个 Trigger 变量,这个 trigger 里面可以获取 cron 的下一次执行的时间。
  • IntervalTask 周期任务是固定速率任务和固定延时任务的父类,这个类里面需要设置 initialDelay(初始延时) 和 interval(周期)。

最后来看下底层的三个实现类:

public class CronTask extends TriggerTask {// cron 表达式private final String expression;public CronTask(Runnable runnable, String expression) {this(runnable, new CronTrigger(expression));}public CronTask(Runnable runnable, CronTrigger cronTrigger) {super(runnable, cronTrigger);this.expression = cronTrigger.getExpression();}/*** Return the cron expression defining when the task should be executed.*/public String getExpression() {return this.expression;}}public class FixedRateTask extends IntervalTask {public FixedRateTask(Runnable runnable, long interval, long initialDelay) {super(runnable, interval, initialDelay);}}public class FixedDelayTask extends IntervalTask {public FixedDelayTask(Runnable runnable, long interval, long initialDelay) {super(runnable, interval, initialDelay);}}

其实类里面的结构不复杂,直接看代码就能看明白了。

6. ScheduledTaskRegistrar

6.1 添加任务的逻辑

在 ScheduledAnnotationBeanPostProcessor 初始化完之后,被 @Scheduled 注解标注的方法会被封装成 Task 存到集合中,那么这些方法是什么时候被调度的,就在这个类里面会解答。在看里面的方法之前,还是先把目光 ScheduledAnnotationBeanPostProcessor 里面,processScheduled 方法中,不管是注册 cron、fixRate、fixDelay 类型的任务,都会通过 registrar 里面的方法去创建。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个 registrar 其实就是 ScheduledTaskRegistrar,那么我们就跟进这几个方法去看看是怎么处理任务的。

/**
* 调度 cron 表达式任务的逻辑* @param task* @return*/
@Nullable
public ScheduledTask scheduleCronTask(CronTask task) {// 从未解析任务集合中移除任务ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;// 如果不存在 ScheduledTask,就创建一个if (scheduledTask == null) {// ScheduledTask 是对 Task 任务的包装scheduledTask = new ScheduledTask(task);// 表示新建了一个 ScheduledTask 任务newTask = true;}// 如果调度器不为空if (this.taskScheduler != null) {// 调度器调度任务scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else {// 此时调度器还没有初始化,先把任务存起来// 1.添加到 cronTasks 中// 2.添加到 unresolvedTasks 中addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}public void addCronTask(CronTask task) {if (this.cronTasks == null) {this.cronTasks = new ArrayList<>();}this.cronTasks.add(task);
}

首先是 scheduleCronTask 方法,这个方法会传入一个 cron 表达式任务,流程是这样的:

  1. 先从 unresolvedTasks 集合中获取这个任务对应的 ScheduledTask,Spring 里面的定时任务都是统一用 ScheduledTask 来调度的,ScheduledTask 里面会封装 CronTask、FixedRateTask、FixedDelayTask
  2. 如果不存在对应的 scheduleCronTask,就创建一个
  3. 如果还没有初始化调度器,说明定时任务还没能调度任务,这时候先把任务加入到 unresolvedTasks 和 cronTasks 集合中
  4. 如果初始化调度器了,就通过调度器去调度 cron 任务
  5. 最后返回任务,如果是新建的就返回新建的任务

这个就是调度 CronTask 的流程,可以看到在里面如果调度器没有初始化的时候是不会调度任务的,只会把任务添加到集合中,等调度器 taskScheduler 初始化之后再调度任务。再看下其他两个调度方法。

/*** 调度固定延时任务* @param task* @return*/
@Nullable
public ScheduledTask scheduleFixedDelayTask(FixedDelayTask task) {// 从未解析任务集合中移除任务ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {// 如果不存在就创建一个scheduledTask = new ScheduledTask(task);newTask = true;}// 如果设置调度器了if (this.taskScheduler != null) {if (task.getInitialDelay() > 0) {// 启动时间是当前时间 + 延时时间Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay());// 通过调度器去调度固定延时任务scheduledTask.future =this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());}else {// 如果没有设置初始延时时间,直接执行固定延时任务scheduledTask.future =this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval());}}else {// 如果调度器还没有初始化,就先把任务存起来addFixedDelayTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}/*** Schedule the specified fixed-rate task, either right away if possible* or on initialization of the scheduler.* @return a handle to the scheduled task, allowing to cancel it* (or {@code null} if processing a previously registered task)* @since 5.0.2*/
@Nullable
public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {// 从未调度任务中获取需要调度的固定速率任务ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {// 如果获取不到就创建一个scheduledTask = new ScheduledTask(task);newTask = true;}// 使用调度器去调度任务if (this.taskScheduler != null) {if (task.getInitialDelay() > 0) {// 第一次执行的时间 = 当前时间 + @Scheduled 的 initialDelayDate startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay());scheduledTask.future =// 进行调度this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());}else {scheduledTask.future =this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());}}else {// 调度器还没有设置,先存到没有调度集合中addFixedRateTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

这两个方法跟上面的 scheduleCronTask 逻辑是一样的。

6.2 调度器初始化

上面就是在 ScheduledTaskRegistrar 中的调度任务的逻辑,只是由于调度器还没有初始化,所以任务还不能被调度。那么调度器是在哪被初始化的呢?

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {...
}

可以看到 ScheduledTaskRegistrar 实现了 InitializingBean,在初始化之后会调用 afterPropertiesSet

/*** Calls {@link #scheduleTasks()} at bean construction time.* 初始化之后调用*/
@Override
public void afterPropertiesSet() {scheduleTasks();
}/**
* Schedule all registered tasks against the underlying* {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.*/
@SuppressWarnings("deprecation")
protected void scheduleTasks() {// 如果我们没有自己设置 taskSchedulerif (this.taskScheduler == null) {// 单线程的线程池this.localExecutor = Executors.newSingleThreadScheduledExecutor();// 这里默认就创建一个 ConcurrentTaskScheduler,使用单线程的线程池,其实这个线程池类型就是 ScheduledExecutorServicethis.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);}// 把需要调度的任务添加到 scheduleTasks 中if (this.triggerTasks != null) {for (TriggerTask task : this.triggerTasks) {addScheduledTask(scheduleTriggerTask(task));}}// 把需要调度的 cron 任务添加到 scheduleTasks 中if (this.cronTasks != null) {for (CronTask task : this.cronTasks) {addScheduledTask(scheduleCronTask(task));}}// 把需要调度的固定速率任务添加到 scheduleTasks 中if (this.fixedRateTasks != null) {for (IntervalTask task : this.fixedRateTasks) {addScheduledTask(scheduleFixedRateTask(task));}}// 把需要调度的固定延时任务添加到 scheduleTasks 中if (this.fixedDelayTasks != null) {for (IntervalTask task : this.fixedDelayTasks) {addScheduledTask(scheduleFixedDelayTask(task));}}
}

这个方法会调度所有的任务,可以看到,如果我们没有设置调度器 TaskScheduler,就设置一个单线程的 ScheduledExecutorService 作为任务执行的线程池。所以 @Scheduled 的核心调度是通过 ScheduledExecutorService 来调度的。
还记得上面 processScheduled 方法吗,在这个方法中

  1. CronTask 被添加到 ScheduledTaskRegistrar 的 cronTasks 集合中
  2. FixedDelayTask 被添加到 ScheduledTaskRegistrar 的 fixedDelayTasks 集合中
  3. FixedRateTask 被添加到 ScheduledTaskRegistrar 的 fixedRateTasks 集合中

在上面的 scheduleTasks 方法中,调度的任务顺序是:

  1. triggerTasks
  2. cronTasks
  3. fixedRateTasks
  4. fixedDelayTasks

triggerTasks 是接口实现类的扩展点,上一篇文章中我们通过接口实现动态配置定时任务的时候,就是通过 addTriggerTask 方法把任务添加到 triggerTasks 集合里面。

public abstract class ScheduledConfig implements SchedulingConfigurer {// 定时任务周期表达式private String cron;@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {// 设置线程池,可开启多线程taskRegistrar.setScheduler(taskExecutor());taskRegistrar.addTriggerTask(// 执行定时任务() -> {taskService();},triggerContext -> {// 这里就是动态获取任务队列的逻辑cron = getCron();if(cron == null){throw new RuntimeException("cron not exist");}// 重新获取cron表达式CronTrigger trigger = new CronTrigger(cron);return trigger.nextExecutionTime(triggerContext);});}private Executor taskExecutor() {return BeanUtils.getBean(ThreadPoolTaskScheduler.class);}public abstract void taskService();public abstract String getCron();public abstract int getOpen();
}

所以我们通过 addTriggerTask 添加的任务会在这里被调度,先看调度的逻辑。

@Nullable
public ScheduledTask scheduleTriggerTask(TriggerTask task) {// 从未解析任务集合中获取 ScheduledTaskScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {// 如果不存在就创建一个scheduledTask = new ScheduledTask(task);newTask = true;}// 这里调度器已经初始化了if (this.taskScheduler != null) {// 通过调度器去调度任务scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else {// 否则就把任务添加到集合中addTriggerTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

这个方法我们上面已经看过了,基本是一样的逻辑,只不过在这里 taskScheduler 已经被初始化了,所以这里会通过 ScheduledExecutorService 去调度任务。scheduleCronTask、scheduleFixedRateTask、scheduleFixedDelayTask 的逻辑上面已经说了,这里就不多说了。

6.3 调用时机

上面我们说了 ScheduledTaskRegistrar 的初始化逻辑,那么你有没有想过,ScheduledAnnotationBeanPostProcessor 是怎么和 ScheduledTaskRegistrar 联系起来的,要知道如果 ScheduledTaskRegistrar 是初始化之后调用的 afterPropertiesSet,那跟 ScheduledAnnotationBeanPostProcessor 的初始化就控制不了先后顺序,因为肯定要 ScheduledAnnotationBeanPostProcessor 先初始化解析任务,然后再通过 ScheduledTaskRegistrar 去调度的。并且 ScheduledAnnotationBeanPostProcessor 里面并没有通过 spring 的方式引入 ScheduledTaskRegistrar。
其实 ScheduledAnnotationBeanPostProcessor 里面的 registrar 和 Spring 初始化的 ScheduledTaskRegistrar 不是同一个。当 ScheduledAnnotationBeanPostProcessor 被创建出来时,构造器中就会通过 new 的方式创建出 ScheduledTaskRegistrar。

public ScheduledAnnotationBeanPostProcessor() {this.registrar = new ScheduledTaskRegistrar();}

好了,不卖关子,当 ScheduledAnnotationBeanPostProcessor 创建完成之后,Spring 上下文启动之后会回调 onApplicationEvent 方法。

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {finishRegistration();}
}

在这个 onApplicationEvent 中会调用 finishRegistration,下面就看下这个方法的逻辑。

private void finishRegistration() {// 如果设置了调度器if (this.scheduler != null) {// 把调度器设置到 registrar 里面this.registrar.setScheduler(this.scheduler);}// BeanFactory 一般都会是这个类型if (this.beanFactory instanceof ListableBeanFactory) {// 获取所有 SchedulingConfigurer 类型的 beanMap<String, SchedulingConfigurer> beans =((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());// 进行排序AnnotationAwareOrderComparator.sort(configurers);// 然后遍历这些实现类for (SchedulingConfigurer configurer : configurers) {// 调用 configureTasks 方法configurer.configureTasks(this.registrar);}}// 如果实现类里面添加了任务并且没有设置 taskSchedulerif (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");try {// 从 Spring 里面找到 TaskScheduler 类型的 bean,然后设置到当前调度器上面this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));}catch (NoUniqueBeanDefinitionException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +ex.getMessage());}try {this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));}catch (NoSuchBeanDefinitionException ex2) {if (logger.isInfoEnabled()) {logger.info("More than one TaskScheduler bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +ex.getMessage());}// Search for ScheduledExecutorService bean next...try {this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));}catch (NoUniqueBeanDefinitionException ex2) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +ex2.getMessage());}try {this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));}catch (NoSuchBeanDefinitionException ex3) {if (logger.isInfoEnabled()) {logger.info("More than one ScheduledExecutorService bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex2.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex2) {if (logger.isTraceEnabled()) {logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +ex2.getMessage());}// Giving up -> falling back to default scheduler within the registrar...logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");}}}// 再调用 afterPropertiesSet 方法this.registrar.afterPropertiesSet();
}

在这个方法中会从 Spring 容器里面获取所有实现了 SchedulingConfigurer 的类,然后调用这些类的 configureTasks 方法,我们设置的 Executor 和 triggerTask 就是在这里添加到调度器中的。最后调用调度器的 afterPropertiesSet 方法,在这个方法里面通过调度器调度任务。
好了,到这里我们就看到了,调度器是如何初始化的,同时也看到了添加的任务是如何被调度的。那么我们知道 ScheduledTaskRegistrar 里面的 taskScheduler 有很多种类型,这些类型有什么不同呢?

7. taskScheduler 类型

我们先来看下这个方法,在我们自己实现的 SchedulingConfigurer 接口类中,就通过 taskRegistrar.setScheduler(taskExecutor()) 添加了一个调度器。上面 6.3 的逻辑中如果没有设置这玩意,就会到 Spring 里面根据类型或者名字去找。如果设置了就不会,就用我们自己设置的。
当我们自己设置了 taskScheduler,ScheduledTaskRegistrar 就不会再创建一个默认的单线程的 ConcurrentTaskScheduler 作为 taskScheduler 去执行任务。

/**
* 设置任务调度器,当我们使用接口动态配置的时候* @param scheduler*/
public void setScheduler(@Nullable Object scheduler) {if (scheduler == null) {this.taskScheduler = null;}else if (scheduler instanceof TaskScheduler) {this.taskScheduler = (TaskScheduler) scheduler;}else if (scheduler instanceof ScheduledExecutorService) {// JDK 的 ScheduledExecutorService 类型,需要封装this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));}else {throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());}
}

其实说到底 taskScheduler 也就是上面这三种类型。

7.1 ConcurrentTaskScheduler

在这里插入图片描述
我们直接看实现类,其实这几个都是差不多的,我们直接看 ConcurrentTaskScheduler 的。因为上面代码中如果 scheduler instanceof ScheduledExecutorService,就会封转成 ConcurrentTaskScheduler 类型,我们先看下构造器。

public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor) {// 父类是 ConcurrentTaskExecutorsuper(scheduledExecutor);// 初始化调度器initScheduledExecutor(scheduledExecutor);
}private void initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {if (scheduledExecutor != null) {// 设置调度器this.scheduledExecutor = scheduledExecutor;// ManagedScheduledExecutorService 是 Java 并发实用工具(Concurrency Utilities)为 Java EE 环境提供的一种可管理的调度执行服务// 扩展了 Java SE 的 ScheduledExecutorService,为在 Java EE 环境中提交延迟或周期性任务的执行提供了额外的方法和管理功能this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&managedScheduledExecutorServiceClass.isInstance(scheduledExecutor));}else {// 创建一个单线程的 ScheduledExecutorServicethis.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();this.enterpriseConcurrentScheduler = false;}
}

这个方法接收了一个 ScheduledExecutorService ,设置到父类里面,然后初始化这个调度器。如果我们没有设置 scheduledExecutor,就会创建一个单线程的 ScheduledExecutorService。
然后再看下核心的调度方法,也就是 schedule 方法:

/**
* 调度任务的方法* @param task the Runnable to execute whenever the trigger fires* @param trigger an implementation of the {@link Trigger} interface,* e.g. a {@link org.springframework.scheduling.support.CronTrigger} object* wrapping a cron expression* @return*/
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {try {// 如果设置了 ManagedScheduledExecutorService,就用 EnterpriseConcurrentTriggerScheduler 去调度,一般也用不上if (this.enterpriseConcurrentScheduler) {return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);}else {// 使用 ReschedulingRunnable 去调度,这个 errorHandler 是处理异常的ErrorHandler errorHandler =(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));return new ReschedulingRunnable(task, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule();}}catch (RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);}
}/*** 调度任务* @param task the Runnable to execute whenever the trigger fires* @param startTime the desired execution time for the task* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)* @return*/
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {// 获取当前任务还有多久执行long delay = startTime.getTime() - this.clock.millis();try {// 使用 ScheduledExecutorService 去执行延时任务return this.scheduledExecutor.schedule(decorateTask(task, false), delay, TimeUnit.MILLISECONDS);}catch (RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);}
}@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {long initialDelay = startTime.getTime() - this.clock.millis();try {return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);}catch (RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);}
}@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {try {return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period, TimeUnit.MILLISECONDS);}catch (RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);}
}

这里面 scheduleAtFixedRate 其实就是调用的 ScheduledExecutorService 的方法来调度任务的。而对于 schedule 方法,会通过创建 ReschedulingRunnable 去调度任务。那我们就看下 ReschedulingRunnable 的 schedule 方法。

@Nullable
public ScheduledFuture<?> schedule() {synchronized (this.triggerContextMonitor) {// 计算出下一次的调用时间this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);if (this.scheduledExecutionTime == null) {return null;}// 获取下一次执行时间距离当前的延时long delay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();// 调用 ScheduledExecutorService 方法去调度,延时时间是 delaythis.currentFuture = this.executor.schedule(this, delay, TimeUnit.MILLISECONDS);return this;}
}

在这个方法中通过触发器获取下一次任务的执行事件,然后计算出当前时间距离下一次任务执行时间的延时,最后通过 ScheduledExecutorService 去调度任务。然后再来看下这个 ReschedulingRunnable 的 run 方法,因为调度任务其实就是执行 run 方法。

/*** 任务调度的核心逻辑*/
@Override
public void run() {// 获取当前的时间Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());// 执行父类的方法,父类其实就是调用 task.run,也就是调用我们设置的 @Scheduled 方法super.run();// 任务的完成时间Date completionTime = new Date(this.triggerContext.getClock().millis());synchronized (this.triggerContextMonitor) {Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");// 更新调度器上下文,也就是调度时间、实际的调度时间、完成时间this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);// 如果任务没有取消if (!obtainCurrentFuture().isCancelled()) {// 在里面通过 ScheduledExecutorService 进行方法的调度// 也就是说 spring 启动的时候会先调用一次 run 方法,后续再通过 ScheduledExecutorService 调度任务schedule();}}
}

方法很简单,就是执行具体的任务逻辑,然后更新调度器上下文,把调度时间、实际的调度时间、完成时间都存起来,接着再调用上面的 schedule() 方法,再把任务重新放到 ScheduledExecutorService 里面去执行。

7.2 ThreadPoolTaskScheduler

好了,上面的 ConcurrentTaskScheduler 核心逻辑已经说完了,下面再来看下 ThreadPoolTaskScheduler 的逻辑,因为我们在自定义 SchedulingConfigurer 实现类中设置了ThreadPoolTaskScheduler 作为调度类,所以这里简单看下这个调度类是怎么调度任务的。

public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupportimplements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {...
}

ThreadPoolTaskScheduler 里面没有提供构造器,就是使用的无参构造器,我们看下自定义 SchedulingConfigurer 实现类中是怎么设置这玩意的。

@Configuration
public class BeanConfig {@Beanpublic ThreadPoolTaskScheduler taskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setPoolSize(10); // 设置线程池大小scheduler.setThreadNamePrefix("MyTaskScheduler-"); // 设置线程名称前缀scheduler.initialize(); // 初始化调度器return scheduler;}
}private Executor taskExecutor() {return BeanUtils.getBean(ThreadPoolTaskScheduler.class);
}

我们在 BeanConfig 里面注册了类型为 ThreadPoolTaskScheduler 的 bean,然后在 ScheduledConfig 里面通过 BeanUtils.getBean 获取我们注册的 bean。
所以我们就看下 ThreadPoolTaskScheduler 的 父类 ExecutorConfigurationSupport 里面的 afterPropertiesSet()。因为 Spring 创建 ThreadPoolTaskScheduler 的时候会去创建父类 ExecutorConfigurationSupport,而父类 ExecutorConfigurationSupport 实现了 InitializingBean 接口,实现 afterPropertiesSet() 方法。

@Override
public void afterPropertiesSet() {initialize();
}public void initialize() {if (logger.isDebugEnabled()) {logger.debug("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));}if (!this.threadNamePrefixSet && this.beanName != null) {setThreadNamePrefix(this.beanName + "-");}this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
}

其实 ThreadPoolTaskScheduler 里面调度任务用的就是 ExecutorConfigurationSupport 里面的 executor。我们看下这个 executor 的初始化逻辑。

@Override
protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {ScheduledThreadPoolExecutor scheduledPoolExecutor = (ScheduledThreadPoolExecutor) this.scheduledExecutor;if (this.removeOnCancelPolicy) {scheduledPoolExecutor.setRemoveOnCancelPolicy(true);}if (this.continueExistingPeriodicTasksAfterShutdownPolicy) {scheduledPoolExecutor.setContinueExistingPeriodicTasksAfterShutdownPolicy(true);}if (!this.executeExistingDelayedTasksAfterShutdownPolicy) {scheduledPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);}}return this.scheduledExecutor;
}protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
}

在初始化 ThreadPoolTaskScheduler 方法中会创建一个 ScheduledExecutorService 来调度任务。然后配置一些参数。

8. 小结

到这里我们终于把 @Scheduled 的原理学习完了,其实这个注解最底层还是用的 ScheduledExecutorService 来调度。







如有错误,欢迎指出!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/889871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JumpServer开源堡垒机搭建及使用

目录 一,产品介绍 二,功能介绍 三,系统架构 3.1 应用架构 3.2 组件说明 3.3 逻辑架构 3.3 逻辑架构 四,linux单机部署及方式选择 4.1 操作系统要求(JumpServer-v3系列版本) 4.1.1 数据库 4.1.3创建数据库参考 4.2 在线安装 4.2.1 环境访问 4.3 基于docker容…

单目动态新视角合成

目录 单目动态新视角合成 Generative Camera Dolly:Extreme Monocular Dynamic Novel View Synthesis 单目动态新视角合成 Generative Camera Dolly: Extreme Monocular Dynamic Novel View Synthesis Generative Camera Dolly: Extreme Monocular Dynamic Novel View Synth…

ResNet网络:深度学习中的革命性架构

目录 ​编辑 引言 ResNet网络的特点 1. 残差块&#xff08;Residual Block&#xff09; 2. 恒等映射&#xff08;Identity Mapping&#xff09; 3. 深层网络训练 4. Batch Normalization 5. 全局平均池化 6. 灵活的结构 ResNet的应用案例 ResNet的研究进展 实战案例…

如何在Playwright中操作窗口的变化

Playwright 是一个用于自动化 Web 应用测试的现代工具支持多种语言&#xff08;包括 Java&#xff09;及多个浏览器。它提供了一致的 API 来控制浏览器行为&#xff0c;其中包括窗口操作&#xff0c;如最大化。本文将详细介绍如何在 Java Playwright 中实现浏览器窗口的最大化 …

【GoF23种设计模式】02_单例模式(Singleton Pattern)

文章目录 前言一、什么是单例模式&#xff1f;二、为什么要用单例模式&#xff1f;三、如何实现单例模式&#xff1f;总结 前言 提示&#xff1a;设计者模式有利于提高开发者的编程效率和代码质量&#xff1a; GoF&#xff08;Gang of Four&#xff0c;四人帮&#xff09;设计…

Node.js day-01

01.Node.js 讲解 什么是 Node.js&#xff0c;有什么用&#xff0c;为何能独立执行 JS 代码&#xff0c;演示安装和执行 JS 文件内代码 Node.js 是一个独立的 JavaScript 运行环境&#xff0c;能独立执行 JS 代码&#xff0c;因为这个特点&#xff0c;它可以用来编写服务器后端…

又要考试了

一、实现无名管道练习&#xff1a;父进程写入管道&#xff0c;子进程读取管道数据。 #include<myhead.h> int main(int argc, const char *argv[]) {int fd[2];char buff[1024]"王吕阳&#xff0c;崔庆权别卷了";char s[1024];if(pipe(fd)-1){perror("pi…

LoadBalancer负载均衡和Nginx负载均衡区别理解

LoadBalancer和Nginx都是用来做负载均衡用的&#xff0c;那二者有什么区别呢&#xff1f; Nginx服务器端的负载均衡&#xff1a; 所有请求都先发到nginx&#xff0c;然后再有nginx转发从而实现负载均衡。LoadBalancer是本地的负载均衡&#xff1a; 它是本地先在调用微服务接口…

Linux shell脚本练习(六)

清除系统默认文件缓存/tmp中超过30天未访问的文件 #!/bin/bash# 临时文件存放的目录 TEMP_DIR"/tmp" # 设置保留文件的天数 RETENTION_DAYS30# 判断临时目录是否存在 if [ ! -d "$TEMP_DIR" ]; thenecho "临时目录 $TEMP_DIR 不存在&#xff01;&quo…

【MQTT 编程】-API

文章目录 1 MQTTClient_message 结构体2 创建客户端对象3 连接服务端3 设置回调函数4 发布消息5 订阅主题和取消订阅主题5.1 订阅主题5.2 取消订阅 6 断开服务连接 1 MQTTClient_message 结构体 很重要的结构体&#xff0c;客户端应用程序发布消息和接收消息都是围绕这这个结构…

Technitium DNS Server的基本使用1(创建主区域,A记录,开启递归查询,递归到114.114.114.114)

Technitium DNS Server Technitium DNS Server搭建 搭建请看博主的上篇博客&#xff0c;内外网的方法都有 链接: 内网搭建Technitium DNS Server详细教程 登陆进去是以下界面 这个界面主要是监控&#xff0c;有访问的时候就会有波动 创建主区域&#xff0c;A记录 写上主区…

OpenAI 与 ChatGPT 的关系解析

OpenAI 与 ChatGPT 的关系解析 基本关系 OpenAI 是公司&#xff0c;ChatGPT 是产品 OpenAI 是一家人工智能研究公司ChatGPT 是 OpenAI 开发的一款 AI 聊天产品ChatGPT 使用的是 OpenAI 开发的 GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型 OpenAI 的…

Git简介和特点

目录 一、Git简介 二、Git特点 1.集中式和分布式 (1)集中式版本控制系统 (2)分布式版本控制系统 2.版本存储方式的差异 (1)直接记录快照&#xff0c;而非差异比较 3.近乎所有操作都是本地执行 一、Git简介 Git是目前世界上最先进的的分布式控制系统&#xff08;没有之一…

CSS学习记录15

CSS下拉菜单 使用CSS创建可悬停的下拉列表。 下拉式式菜单 .dropdown类使用position:relative,当我们希望将下拉内容放置在下拉按钮的正下方(使用position:absolute)时&#xff0c;需要使用该类。 .dropdown-content 类保存实际的下拉内容。默认情况下它是隐藏的&#xff0…

《国产单片机,soc的一些现实问题》

大概从口罩开始&#xff0c;芯片断供。在中低端市场&#xff0c;国外mcu&#xff0c;国外soc趁机抢占了大量市场份额。 但是因为大家都用国外了&#xff0c;价格优势依然不明显。 有一些没有核心技术的公司&#xff0c;或老板业务或采购出身&#xff0c;不懂技术。 在一堆芯片面…

AdminJS - 现代化的 Node.js 管理面板框架详解

AdminJS - 现代化的 Node.js 管理面板框架详解 什么是 AdminJS? AdminJS 是一个自动化的管理面板框架&#xff0c;专为 Node.js 应用程序设计。它可以让开发者快速构建功能强大的管理后台界面&#xff0c;而无需编写大量重复的代码。 主要特点 自动 CRUD 操作 自动生成增删…

RabbitMQ全局流量控制

RabbitMQ全局流量控制 流控机制流控是对什么进行控制&#xff1f;rabbitmq进程邮箱流控机制是什么&#xff1f; 流控原理流控原理流程 流控状态显示流控对象流控机制对象主要进程各进程状态情形分析 性能提升提升队列性能方式 当消息积压时&#xff0c;消息会进入到队列深处&am…

大数据平台

大数据行业应用持续升温&#xff0c;特别是企业级大数据市场正在进入快速发展时期。越来越多的企业期望实现数据孤岛的打通&#xff0c;整合海量的数据资源&#xff0c;挖掘并沉淀有价值的数据&#xff0c;进而驱动更智能的商业。随着公司数据爆发式增长&#xff0c;原有的数据…

鸿蒙生态的崛起:开发实践、认证路径与激励策略

目录 前言 鸿蒙生态能力和行业解决方案 1、鸿蒙创新能力 2、鸿蒙行业解决方案 中软鸿蒙生态业务布局 1、深度参与鸿蒙生态建设 2、提供一站式鸿蒙生态服务 &#xff08;1&#xff09;服务目录 &#xff08;2&#xff09;改造过程的关键点 &#xff08;3&#xff09;鸿…

Vue如何来处理动画

Vue 提供了多种方式来处理动画&#xff0c;使得创建动态用户界面变得简单而灵活。以下是几种关于 Vue 动画的技巧和最佳实践&#xff0c;帮助你更高效地在项目中实现动画效果&#xff1a; 1. 使用 <transition> 和 <transition-group> 组件 Vue 内置的 <trans…