Spring 原理之 @Scheduled(定时任务)

Spring 原理之 @Scheduled

前言

在开发过程中,我们会用一些简单的定时任务来实现操作,例如定时去捞取流水重试业务、定时去消息中间件获取消息等等相关需求

简单的定时任务实现可以借助Spring提供的 @Scheduled 注解

需要注意的是这些功能都是Spring Framework提供的,而非SpringBoot。因此下文的讲解都是基于Spring Framework的工程

Spring中用**@Scheduled** 注解标记的方法,称为定时任务,它会在调用方的当前线程之外的独立的线程中执行,其实就相当于我们自己new Thread(()-> System.out.println(“hello world !”))这样在另一个线程中去执行相应的业务逻辑,下面来看看它怎么用,原理是啥?

Demo

// @Scheduled可把注解放在 方法 和 注解类型上,一般用在方法上
@Slf4j
@Component
public class ScheduleConfig {

    @Scheduled(cron = "1 * * * * ?")
    public void exampleSchedule() throws Exception{
        log.info("cron run");
    }
}

然后只需要在配置里,开启对定时任务的支持即可:

@EnableScheduling // 开启定时任务注解的支持
@SpringBootApplication
public class MainApplication {

   public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

输出如下:(每分钟执行一次)

2022-04-21 11:01:01.012  INFO 4724 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run
2022-04-21 11:02:01.014  INFO 4724 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run

@Scheduled注解源码

  • org.springframework.scheduling.annotation.Scheduled
//  org.springframework.scheduling.annotation; 包下面的

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String CRON_DISABLED = "-";

    // 任务执行的cron表达式     ex: 0/1 * * * * ?
    String cron() default "";
	// cron表达时解析使用的时区    默认为服务器的本地时区
    String zone() default "";
	// 上一次任务执行结束到下一次执行开始的间隔时间, 单位为ms ex: 1000
    long fixedDelay() default -1L;
	// 上一次任务执行结束到下一次执行开始的间隔时间, 使用java.time.Duration#parse解析
    String fixedDelayString() default "";
    // 上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加worker队列,等待上一次执行完成后立即执行下一次任务	ex: 2000
    long fixedRate() default -1L;
	// 使用java.time.Duration#parse解析的 fixedRate
    String fixedRateString() default "";
	// 首次任务执行的延迟时间, 单位为ms
    long initialDelay() default -1L;
	// 首次任务执行的延迟时间,使用java.time.Duration#parse解析
    String initialDelayString() default "";
}

说明:

  • 注解用在 方法注解类型
  • cron 表达式 具体编写需自己去学习一下 Cron表达式

原理解析

这里涉及到 Bean 的生命周期的相关知识,如若不了解 可查看 : Bean的生命周期

EnableScheduling

它位于的包名为org.springframework.scheduling.annotation,jar名为:spring-context

@EnableXXX 这种设计模式之前有分析过多次,这个注解就是它的入口,因此本文也一样,从入口处一层一层的剖析:

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

最重要的,还是上面的@Import注解导入的类:SchedulingConfiguration

SchedulingConfiguration

  • org.springframework.scheduling.annotation.SchedulingConfiguration
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
	// name = "org.springframework.context.annotation.internalScheduledAnnotationProcessor"
	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    // Role : 2
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}

}

可以看到,基本上 @Enablexxx 都是会有一个后置处理器 xxxBeanPostProcessor 来处理业务

ScheduledAnnotationBeanPostProcessor

  • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
    Spring 原理之 @Scheduled(定时任务)_第1张图片

在后置处理器核心方法就是在 初始化Bean的 前后两个方法

  • postProcessBeforeInitialization:初始化之前的操作
  • postProcessAfterInitialization:初始化之后的操作 核心处理方法为 processScheduled
// 初始化之前不作任何操作
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
   return bean;
}
// 初始化之前进行操作
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
   // 如果bean是已经具备定时功能,直接返回
   if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
         bean instanceof ScheduledExecutorService) {
      // Ignore AOP infrastructure such as scoped proxies.
      return bean;
   }
   Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
   if (!this.nonAnnotatedClasses.contains(targetClass) &&
         AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
      Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
            (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
               Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                     method, Scheduled.class, Schedules.class);
               return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
            });
      if (annotatedMethods.isEmpty()) {
         this.nonAnnotatedClasses.add(targetClass);
         if (logger.isTraceEnabled()) {
            logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
         }
      } 
       // 如果这个bean里面有方法加了@Schedule注解,就对标注注解的方法执行 processScheduled 核心方法
      else {
         // Non-empty set of methods
         annotatedMethods.forEach((method, scheduledMethods) ->
               scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
         if (logger.isTraceEnabled()) {
            logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                  "': " + annotatedMethods);
         }
      }
   }
   return bean;
}

processScheduledprocessScheduled :处理核心逻辑

  • 创建 Runnable(线程),为bean中加了@Scheduled注解的方法
  • 判断 @Scheduled注解中属性的值是否合法
    • cron表达式是否合法(如果设置了),如果设置了,ScheduledTask 的实际类型就是 CronTask
    • initialDelayString是否可 parse(如果设置了)
    • fixedDelayString是否可 parse(如果设置了)如果设置了,ScheduledTask 的实际类型就是 FixedDelayTask
    • fixedRateString是否可 parse(如果设置了)如果设置了,ScheduledTask 的实际类型就是 FixedRateTask
  • 注册,就是把 ScheduleTask 放入map中,然后加入到set中

org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled

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

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
   try {
       // 1、创建Runnable
      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);
      // 2、判断@Scheduled 自定义的值,是否合法
      long initialDelay = scheduled.initialDelay();
      String initialDelayString = scheduled.initialDelayString();
      if (StringUtils.hasText(initialDelayString)) {
         Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
         if (this.embeddedValueResolver != null) {
            initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
         }
         if (StringUtils.hasLength(initialDelayString)) {
            try {
               initialDelay = parseDelayAsLong(initialDelayString);
            }
            catch (RuntimeException ex) {
               throw new IllegalArgumentException(
                     "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
            }
         }
      }
      String cron = scheduled.cron();
      if (StringUtils.hasText(cron)) {
         String zone = scheduled.zone();
         if (this.embeddedValueResolver != null) {
            cron = this.embeddedValueResolver.resolveStringValue(cron);
            zone = this.embeddedValueResolver.resolveStringValue(zone);
         }
         if (StringUtils.hasLength(cron)) {
            Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
            processedSchedule = true;
            if (!Scheduled.CRON_DISABLED.equals(cron)) {
               TimeZone timeZone;
               if (StringUtils.hasText(zone)) {
                  timeZone = StringUtils.parseTimeZoneString(zone);
               }
               else {
                  timeZone = TimeZone.getDefault();
               }
               tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
            }
         }
      }
      if (initialDelay < 0) {
         initialDelay = 0;
      }
      // Check fixed delay
      long fixedDelay = scheduled.fixedDelay();
      if (fixedDelay >= 0) {
         Assert.isTrue(!processedSchedule, errorMessage);
         processedSchedule = true;
         tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
      }
      String fixedDelayString = scheduled.fixedDelayString();
      if (StringUtils.hasText(fixedDelayString)) {
         if (this.embeddedValueResolver != null) {
            fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
         }
         if (StringUtils.hasLength(fixedDelayString)) {
            Assert.isTrue(!processedSchedule, errorMessage);
            processedSchedule = true;
            try {
               fixedDelay = parseDelayAsLong(fixedDelayString);
            }
            catch (RuntimeException ex) {
               throw new IllegalArgumentException(
                     "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
            }
            tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
         }
      }

      // Check fixed rate
      long fixedRate = scheduled.fixedRate();
      if (fixedRate >= 0) {
         Assert.isTrue(!processedSchedule, errorMessage);
         processedSchedule = true;
         tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
      }
      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 {
               fixedRate = parseDelayAsLong(fixedRateString);
            }
            catch (RuntimeException ex) {
               throw new IllegalArgumentException(
                     "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
            }
            tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
         }
      }

      // Check whether we had any attribute set
      Assert.isTrue(processedSchedule, errorMessage);

      // 3、注册
      synchronized (this.scheduledTasks) {
         Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
         regTasks.addAll(tasks);
      }
   }
   catch (IllegalArgumentException ex) {
      throw new IllegalStateException(
            "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
   }
}

上面用到的 ScheduledTask 与 Task ,其实就是Runnable 来开启线程~

  • org.springframework.scheduling.config.ScheduledTask
  • org.springframework.scheduling.config.Task
public final class ScheduledTask {
   private final Task task;
   @Nullable
   volatile ScheduledFuture<?> future;
   ScheduledTask(Task task) {
      this.task = task;
   }
   public Task getTask() {
      return this.task;
   }
   public void cancel() {
      ScheduledFuture<?> future = this.future;
      if (future != null) {
         future.cancel(true);
      }
   }
   @Override
   public String toString() {
      return this.task.toString();
   }
}

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;
	}
	@Override
	public String toString() {
		return this.runnable.toString();
	}
}

ContextLifecycleScheduledTaskRegistrar

org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar

  • afterSingletonsInstantiated:在bean初始化后,执行 scheduleTasks方法进行后续操作 ,该方法在 ContextLifecycleScheduledTaskRegistrar中实现
public class ContextLifecycleScheduledTaskRegistrar extends ScheduledTaskRegistrar implements SmartInitializingSingleton {

   @Override
   public void afterPropertiesSet() {
      // no-op
   }
   // 初始化后结束后,执行scheduleTasks方法来完成上面设置好的task的执行
   @Override	
   public void afterSingletonsInstantiated() {
      scheduleTasks();
   }

}

ContextLifecycleScheduledTaskRegistrar

org.springframework.scheduling.config#scheduleTasks

  • Executors.newSingleThreadScheduledExecutor():创建一个单线程的线程池
  • 将上面 ScheduledAnnotationBeanPostProcessor 里面设置好的 ScheduleTask 加入到线程池中
  • 当有多个定时任务时,任务之间会相互等待,同步执行(因为线程池只只有一个线程
public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {
	// 核心方法
    protected void scheduleTasks() {
       if (this.taskScheduler == null) {
          this.localExecutor = Executors.newSingleThreadScheduledExecutor();
           //   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
           //     	return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1)); //单一线程
           //   }
          this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
       }
       if (this.triggerTasks != null) {
          for (TriggerTask task : this.triggerTasks) {
             addScheduledTask(scheduleTriggerTask(task));
          }
       }
       if (this.cronTasks != null) {
          for (CronTask task : this.cronTasks) {
             addScheduledTask(scheduleCronTask(task));
          }
       }
       if (this.fixedRateTasks != null) {
          for (IntervalTask task : this.fixedRateTasks) {
             addScheduledTask(scheduleFixedRateTask(task));
          }
       }
       if (this.fixedDelayTasks != null) {
          for (IntervalTask task : this.fixedDelayTasks) {
             addScheduledTask(scheduleFixedDelayTask(task));
          }
       }
    }
}
private void addScheduledTask(@Nullable ScheduledTask task) {
	if (task != null) {
			this.scheduledTasks.add(task);
	}
}

验证:

@Slf4j
@Component
public class ScheduleConfig {
    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule() throws Exception {
        log.info("cron run one ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule2() {
        log.info("cron run two ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule3() {
        log.info("cron run three ");
    }

}

输出:(都是用 scheduling-1 这个线程来执行的)

2022-04-21 12:53:10.004  INFO 11200 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 12:53:10.005  INFO 11200 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run three 
2022-04-21 12:53:10.008  INFO 11200 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run one 
2022-04-21 12:53:11.015  INFO 11200 --- [   scheduling-1] com.study.config.ScheduleConfig          : cron run three

这样的话,所有@Scheduled注解标注的方法都可以正常定时执行

自定义

1、自定义配置线程池

上面讲解原理的时候可以看到 ScheduledTaskRegistrar 中的 taskScheduler 如果为空的时候, 才会使用默认的单个线程的线程池,那么可以通过设置这个 taskScheduler 来配置我们自己的线程池

if (this.taskScheduler == null) {
          this.localExecutor = Executors.newSingleThreadScheduledExecutor();
           //   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
           //     	return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1)); //单一线程
           //   }
          this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
       }

自定义 ScheduledConfig

package com.study.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    /**
     * 任务执行线程池大小
     */
    private static final int TASK_POOL_SIZE = 50;
    /**
     * 线程名
     */
    private static final String TASK_THREAD_PREFIX = "test-task-";

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        // 指定多个线程的线程池
        ThreadPoolTaskScheduler taskPool = new ThreadPoolTaskScheduler();
        taskPool.setPoolSize(TASK_POOL_SIZE);
        taskPool.setThreadNamePrefix(TASK_THREAD_PREFIX);
        taskPool.initialize();
        // set方法来设置
        scheduledTaskRegistrar.setTaskScheduler(taskPool);
    }
}

再次验证:

@Slf4j
@Component
public class ScheduleConfig {
    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule() throws Exception {
        log.info("cron run one ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule2() {
        log.info("cron run two ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule3() {
        log.info("cron run three ");
    }

}

输出:(我们定义的线程名称前缀 test-task , 三个任务,test-task-1 ~ test-task-3)

2022-04-21 14:03:45.007  INFO 14936 --- [    test-task-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:03:45.007  INFO 14936 --- [    test-task-3] com.study.config.ScheduleConfig          : cron run one 
2022-04-21 14:03:45.007  INFO 14936 --- [    test-task-2] com.study.config.ScheduleConfig          : cron run three 
2022-04-21 14:03:46.007  INFO 14936 --- [    test-task-3] com.study.config.ScheduleConfig          : cron run three 
2022-04-21 14:03:46.008  INFO 14936 --- [    test-task-3] com.study.config.ScheduleConfig          : cron run one 

2、自定义配置注解属性

注解中除了使用cron表达式来设置定时任务时,还可以通过一下属性来设置:

注意:以下属性不支持与cron同时使用,即要么使用cron来设置,要么通过以下属性设置

// 上一次任务执行结束到下一次执行开始的间隔时间, 单位为ms ex: 1000
// 每多少时间执行一次
long fixedDelay() default -1L;
// 上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加worker队列,等待上一次执行完成后立即执行下一次任务	ex: 2000
// 每隔多长时间执行一次
long fixedRate() default -1L;
// 首次任务执行的延迟时间, 单位为ms
long initialDelay() default -1L;

来看看它们怎么用

2.1)initialDelay:首次任务的延迟时间, 单位为ms
  • exampleSchedule: @Scheduled(initialDelay = 3000, fixedRate = 1000)
    • 使用 initialDelay ,第一个任务 3s才执行
    • 使用 fixedRate ,间隔1s 执行一次
  • exampleSchedule2:间隔1s 执行一次
@Slf4j
@Component
public class ScheduleConfig {
    @Scheduled(initialDelay = 3000, fixedRate = 1000)
    public void exampleSchedule() throws Exception {
        log.info("cron run one ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule2() {
        log.info("cron run two ");
    }
}

输出:

2022-04-21 14:12:27.013  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:28.016  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:29.001  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:29.065  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one 
2022-04-21 14:12:30.003  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:30.066  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one

说明:

  • cron run two:第一次是第 27s打印出的
  • cron run one:第一次是第 29s打印出的(过了3s)第二次是第 30s 打印出的,间隔1s
2.2) fixedDelay:上一次任务执行结束到下一次执行开始的间隔时间, 单位为ms
  • exampleSchedule:@Scheduled(fixedDelay = 3000, initialDelay = 1000)
    • 使用 initialDelay ,第一个任务1s后才执行
    • 使用 fixedDelay ,间隔3s 执行一次
  • exampleSchedule2:间隔1s 执行一次
@Slf4j
@Component
public class ScheduleConfig {
    @Scheduled(fixedDelay = 3000, initialDelay = 1000)
    public void exampleSchedule() throws Exception {
        log.info("cron run one ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule2() {
        log.info("cron run two ");
    }
}

输出:

2022-04-21 14:18:50.007  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:18:50.785  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one 
2022-04-21 14:18:51.009  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:18:52.006  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:18:53.010  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:18:53.790  INFO 11156 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one

说明:

  • cron run two:第一次是第 50s打印出的
  • cron run one:第一次是第 50s打印出的,第二次是第 53s 打印出的,过了3s
2.3) fixedRate:上一次任务执行开始到下一次执行开始的间隔时间,单位为ms
  • exampleSchedule: @Scheduled(initialDelay = 3000, fixedRate = 1000)
    • 使用 initialDelay ,第一个任务 3s才执行
    • 使用 fixedRate ,间隔1s 执行一次
  • exampleSchedule2:间隔1s 执行一次
@Slf4j
@Component
public class ScheduleConfig {
    @Scheduled(initialDelay = 3000, fixedRate = 1000)
    public void exampleSchedule() throws Exception {
        log.info("cron run one ");
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void exampleSchedule2() {
        log.info("cron run two ");
    }
}

输出:

2022-04-21 14:12:27.013  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:28.016  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:29.001  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:29.065  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one 
2022-04-21 14:12:30.003  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run two 
2022-04-21 14:12:30.066  INFO 2392 --- [pool-1-thread-1] com.study.config.ScheduleConfig          : cron run one

说明:

  • cron run two:第一次是第 27s打印出的
  • cron run one:第一次是第 29s打印出的(过了3s)第二次是第 30s 打印出的,间隔1s

总结

简单的使用定时任务 基本上使用Spring的Schedule来实现功能即可

但是其不能动态的管理,各个task 的销毁基本上在 Bean的生命周期的销毁阶段,即 DisponsableBean 的 destroy 方法中才会调用后置处理器的destory方法,如果需要动态的管理,可以用 Quartz 来实现 Java - Quarz 定时任务(JobDetail & Job、Trigger、Scheduler)

不了解Bean的生命周期可以查看: Bean的生命周期

org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#destroy

@Override
public void destroy() {
   synchronized (this.scheduledTasks) {
      Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
      for (Set<ScheduledTask> tasks : allTasks) {
         for (ScheduledTask task : tasks) {
            task.cancel();
         }
      }
      this.scheduledTasks.clear();
   }
    // destroy
   this.registrar.destroy();
}

你可能感兴趣的:(学习记录,JAVA,Spring,定时任务,Schedule,java)