精华内容
下载资源
问答
  • scheduled
    千次阅读
    2022-03-27 13:35:24

    我们在平常项目开发中,经常会用到周期性定时任务,这个时候使用定时任务就能很方便的实现。在SpringBoot中用得最多的就是Schedule。

    一、SpringBoot集成Schedule

    1、依赖配置

    由于Schedule就包含在spring-boot-starter中,所以无需引入其他依赖。

    2、启用定时任务

    在启动类或者配置类上增加@EnableScheduling注解。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableScheduling;
    ​
    @EnableScheduling
    @SpringBootApplication
    public class DemoApplication {
    ​
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    }

    3、添加定时任务

    Schdule支持cron表达式、固定间隔时间、固定频率三种调度方式。

    1)cron表达式定时任务

    与Linux下定时任务用到的Cron表达式一样。

    字段允许值允许的特殊字符
    秒(Seconds)0~59的整数, - * / 四个字符
    分(Minutes)0~59的整数, - * / 四个字符
    小时(Hours)0~23的整数, - * / 四个字符
    日期(DayofMonth)1~31的整数(但是你需要考虑该月的天数),- * ? / L W C 八个字符
    月份(Month)1~12的整数或者 JAN-DEC, - * / 四个字符
    星期(DayofWeek)1~7的整数或者 SUN-SAT (1=SUN), - * ? / L C # 八个字符
    年(可选,留空)(Year)1970~2099, - * / 四个字符

    @Component
    @EnableScheduling
    public class MyCronTask {
    ​
      private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class); 
    ​
      @Scheduled(cron = "0/1 * * * * *")
      void cronSchedule(){
        logger.info("cron schedule execute");
      }
    ​
    }

    PS:Cron表达式方式配置的定时任务如果其执行时间超过调度频率时,调度器会在下个执行周期执行。如第一次执行从第0秒开始,执行时长3秒,则下次执行为第4秒。

    2)固定间隔定时任务

    下一次的任务执行时间是从上一次定时任务结束时间开始计算。

    @Scheduled(fixedDelay = 2)
    void fixedDelaySchedule() throws Exception{
      Thread.sleep(2000);
      logger.info("fixed delay schedule execute");
    }

    输出:

    2020-04-23 23:11:54.362 INFO 85325 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed delay schedule execute 2020-04-23 23:11:58.365 INFO 85325 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed delay schedule execute 2020-04-23 23:12:02.372 INFO 85325 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed delay schedule execute 2020-04-23 23:12:06.381 INFO 85325 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed delay schedule execute

    3)固定频率定时任务

    按照指定频率执行任务

    @Scheduled(fixedRate = 2000)
    void fixedRateSchedule() throws Exception{
      Thread.sleep(3000);
      logger.info("fixed rate schedule execute");
    }

    输出:

    2020-04-23 23:16:14.750 INFO 85328 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed rate schedule execute 2020-04-23 23:16:17.754 INFO 85328 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed rate schedule execute 2020-04-23 23:16:20.760 INFO 85328 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed rate schedule execute 2020-04-23 23:16:23.760 INFO 85328 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed rate schedule execute 2020-04-23 23:16:26.764 INFO 85328 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : fixed rate schedule execute

    PS:当方法的执行时间超过任务调度频率时,调度器会在当前方法执行完成后立即执行下次任务。

    二、配置多个定时任务并发执行

    1、并行or串行?

    缺省状态下,当我们没有给定时任务配置线程池时,Schedule是串行执行,如下:

        @Component
    @EnableScheduling
    public class MyCronTask {
    ​
      private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class);
      
      @Scheduled(fixedDelay = 2000)
      void task1Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task1 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task2Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task2 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task3Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task3 execute");
      }
    }

    输出:

    2020-04-23 23:19:46.970 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:19:48.973 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:19:50.974 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:19:52.978 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:19:54.984 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:19:56.984 INFO 85332 --- [ scheduling-1] com.springboot.study.tasks.MyCronTask : task3 execute

    可以看出来只有一个线程穿行执行所有定时任务。

    2、Schedule并行执行配置

    定时调度的并行化,有两种配置方式:

    1)修改任务调度器默认使用的线程池:添加一个configuration,实现SchedulingConfigurer接口就可以了。

    @Configuration
    public class ScheduleConfig implements SchedulingConfigurer{
    ​
      @Override
      public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(getTaskScheduler());
      }
    ​
      @Bean
      public TaskScheduler getTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(3);
        taskScheduler.setThreadNamePrefix("myworker-");
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        return taskScheduler;
      }
    }

    再次执行后,输出:

    2020-04-23 23:33:14.197 INFO 85461 --- [ myworker-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:33:14.197 INFO 85461 --- [ myworker-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:33:14.197 INFO 85461 --- [ myworker-3] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:33:18.203 INFO 85461 --- [ myworker-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:33:18.203 INFO 85461 --- [ myworker-3] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:33:18.204 INFO 85461 --- [ myworker-1] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:33:22.208 INFO 85461 --- [ myworker-1] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:33:22.208 INFO 85461 --- [ myworker-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:33:22.208 INFO 85461 --- [ myworker-3] com.springboot.study.tasks.MyCronTask : task1 execute

    2)直接将任务交给一步线程池处理:启用@EnableAsync注解,并在每一个定时任务方法上使用@Async注解。

    @Component
    @EnableScheduling
    @EnableAsync
    @Async
    public class MyCronTask {
    ​
      private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class);
    ​
      @Scheduled(fixedDelay = 2000)
      void task1Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task1 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task2Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task2 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task3Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task3 execute");
      }
    }

    输出如下:

    2020-04-23 23:38:00.614 INFO 85468 --- [ task-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:38:00.614 INFO 85468 --- [ task-3] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:38:00.614 INFO 85468 --- [ task-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:38:02.620 INFO 85468 --- [ task-4] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:38:02.620 INFO 85468 --- [ task-5] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:38:02.620 INFO 85468 --- [ task-6] com.springboot.study.tasks.MyCronTask : task3 execute

    有上面输出可以看出来这种方式对于每一次定时任务的执行都会创建新的线程,这样对内存资源是一种浪费,严重情况下还会导致服务挂掉,因此为了更好控制线程的使用,我们可以自定义线程池。

    首先配置线程池:

    @Configuration
    public class MyTaskExecutor {
    ​
      @Bean(name = "myExecutor")
      public TaskExecutor getMyExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(20);
        taskExecutor.setThreadNamePrefix("myExecutor-");
        taskExecutor.initialize();
        return taskExecutor;
      }
    }

    使用我们自己的线程池:

    @Component
    @EnableScheduling
    @EnableAsync
    @Async("myExecutor")
    public class MyCronTask {
    ​
      private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class);
    ​
      @Scheduled(fixedDelay = 2000)
      void task1Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task1 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task2Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task2 execute");
      }
    ​
      @Scheduled(fixedDelay = 2000)
      void task3Schedule() throws Exception{
        Thread.sleep(2000);
        logger.info("task3 execute");
      }
    }

    输出:

    2020-04-23 23:46:47.404 INFO 85488 --- [ myExecutor-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:46:47.404 INFO 85488 --- [ myExecutor-3] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:46:47.404 INFO 85488 --- [ myExecutor-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:46:49.404 INFO 85488 --- [ myExecutor-3] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:46:49.404 INFO 85488 --- [ myExecutor-2] com.springboot.study.tasks.MyCronTask : task3 execute 2020-04-23 23:46:49.404 INFO 85488 --- [ myExecutor-1] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:46:51.405 INFO 85488 --- [ myExecutor-2] com.springboot.study.tasks.MyCronTask : task2 execute 2020-04-23 23:46:51.405 INFO 85488 --- [ myExecutor-3] com.springboot.study.tasks.MyCronTask : task1 execute 2020-04-23 23:46:51.405 INFO 85488 --- [ myExecutor-1] com.springboot.study.tasks.MyCronTask : task3 execute

    更多相关内容
  • 主要介绍了spring 定时任务@Scheduled的相关资料,文中通过示例代码介绍的很详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友们下面来一起看看吧。
  • 主要介绍了SpringBoot执行定时任务@Scheduled的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 主要介绍了spring-boot通过@Scheduled配置定时任务,文中还给大家介绍了springboot 定时任务@Scheduled注解的方法,需要的朋友可以参考下
  • 本篇文章中主要介绍了Spring Boot中使用@Scheduled创建定时任务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
  • 主要介绍了Spring boot如何通过@Scheduled实现定时任务及多线程配置,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • redash-scheduled-email

    2021-05-10 10:16:25
    Redash报告电子邮件发件人 创建它是为了每周发送一次redash报告。 设置 确保已安装无服务器,请在此处进行说明: 按照以下模板创建env.yaml: # e.g.... CSV_URL: .... # API key from Mandrill dashboard ...
  • scheduled_tweets

    2021-04-02 01:12:25
    自述文件该自述文件通常会记录启动和运行应用程序所需的所有步骤。 您可能要讲的内容: Ruby版本系统依赖配置数据库创建数据库初始化如何运行测试套件服务(作业队列,缓存服务器,搜索引擎等) 部署说明...
  • 主要介绍了Spring Boot @Scheduled定时任务代码实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了springboot 定时任务@Scheduled实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了SpringBoot中使用@Scheduled注解创建定时任务的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • IDEA使用springboot自带scheduled实现任务调度 代码解析:https://www.cnblogs.com/personblog/p/14237202.html
  • 使用spring @Scheduled注解执行定时任务
  • spring boot工程的定时任务
  • 本篇文章主要介绍了详解在Spring3中使用注解(@Scheduled)创建计划任务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
  • NULL 博文链接:https://rmn190.iteye.com/blog/273564
  • Spring的任务调度@Scheduled注解——task:scheduler和task:executor的解析,做了一些测试说明了各个参数的作用
  • 在不停服务的情况下,动态修改Spring定时任务的执行周期,即动态修改定时任务的cron参数。
  • spring boot @scheduled注解 cron 表达式实现计划任务调度。
  • archiva-scheduled-1.2.jar

    2019-07-18 16:15:44
    标签:archiva-scheduled-1.2.jar,archiva,scheduled,1.2,jar包下载,依赖包
  • archiva-scheduled-1.3.2.jar

    2019-07-18 16:17:10
    标签:archiva-scheduled-1.3.2.jar,archiva,scheduled,1.3.2,jar包下载,依赖包
  • archiva-scheduled-1.1.jar

    2019-07-18 16:13:59
    标签:archiva-scheduled-1.1.jar,archiva,scheduled,1.1,jar包下载,依赖包
  • archiva-scheduled-1.0.2.jar

    2019-07-18 16:10:30
    标签:archiva-scheduled-1.0.2.jar,archiva,scheduled,1.0.2,jar包下载,依赖包
  • Springboot @Scheduled实现原理

    千次阅读 2022-01-17 16:43:21
    创建任务并放入到任务列表依次加载所有的实现Scheduled注解的类方法将对应类型的定时器放入相应的定时任务列表中3.执行定时任务任务列表提交到线程池,任务开始定时执行定时任务线程池执行原理注意事项 实现原理 1....


    实现原理

    1.开启计划任务

    @EnableScheduling注解用于开启计划任务。

       @Target({ElementType.TYPE})
       @Retention(RetentionPolicy.RUNTIME)
       @Import({SchedulingConfiguration.class})
       @Documented
       public @interface EnableScheduling {
       }
    
    @Configuration
    @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();
    	}
     
    }
    

    通过@Import引入了SchedulingConfiguration这个配置类,使这个配置类生效。向spring容器中注入了一个bean:ScheduledAnnotationBeanPostProcessor。

    ScheduledAnnotationBeanPostProcessor

    • ScheduledAnnotationBeanPostProcessor主要实现了两个接口
      • DestructionAwareBeanPostProcessor:继承了BeanPostProcessor类
      • ApplicationListener:监听ContextRefreshedEvent(Spring容器刷新完成事件)

    所以ScheduledAnnotationBeanPostProcessor间接继承了BeanPostProcessor类。

    2.创建任务并放入到任务列表

    Spring在初始化bean后,通过ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法拦截到所有的用到@Scheduled注解的方法,并解析相应的的注解参数,放入“定时任务列表”等待后续处理。

    @Scheduled注解配置可参考文章:@Scheduled注解详解

    ① 依次加载所有的实现Scheduled注解的类方法

    ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) {
       Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
       if (!this.nonAnnotatedClasses.contains(targetClass)) {
          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: " + bean.getClass());
             }
          }
          else {
             // Non-empty set of methods
             annotatedMethods.forEach((method, scheduledMethods) ->
                   scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
             if (logger.isDebugEnabled()) {
                logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                      "': " + annotatedMethods);
             }
          }
       }
       return bean;
    }
    

    遍历了每个bean的方法,并找出使用了@Scheduled的方法,调用processScheduled进行处理。

    ② 将对应类型的定时器放入相应的定时任务列表中

    ScheduledAnnotationBeanPostProcessor的processScheduled方法

    protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
       try {
          Assert.isTrue(method.getParameterCount() == 0,
                "Only no-arg methods may be annotated with @Scheduled");
     
          Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
          Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
          boolean processedSchedule = false;
          String errorMessage =
                "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
     
          Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
     
          // Determine initial delay
          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");
                }
             }
          }
     
          // Check cron expression
          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;
                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))));
             }
          }
     
          // At this point we don't need to differentiate between initial delay set or not anymore
          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);
     
          // Finally register the scheduled tasks
          synchronized (this.scheduledTasks) {
             Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
             if (registeredTasks == null) {
                registeredTasks = new LinkedHashSet<>(4);
                this.scheduledTasks.put(bean, registeredTasks);
             }
             registeredTasks.addAll(tasks);
          }
       }
       catch (IllegalArgumentException ex) {
          throw new IllegalStateException(
                "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
       }
    }
    

    解析@Scheduled注解传进来的参数(cron表达式或者延时等)并创建不同类型的任务提交到registrar中,registrar是用来注册要放到计划任务线程池中运行的任务的,它里面包含一个线程池和任务列表,这一步只是放到了列表中。

    现在@Scheduled已经被解析,并转成了Runable任务对像放到了ScheduledTaskRegistrar对像中了,现在就差把它们放到线程池中启动了。

    3.执行定时任务

    任务列表提交到线程池,任务开始定时执行

    ScheduledAnnotationBeanPostProcessor实现了ApplicationListener的接口。

    当Spring容器初始化完成会触发ContextRefreshedEvent事件,调用ScheduledAnnotationBeanPostProcessor的onApplicationEvent方法,方法调用finishRegistration完成任务列表注册到线程池,任务开始定时执行。

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext() == this.applicationContext) {
            this.finishRegistration();
        }
    }
    
    private void finishRegistration() {
    		if (this.scheduler != null) {
    			this.registrar.setScheduler(this.scheduler);
    		}
     
    		if (this.beanFactory instanceof ListableBeanFactory) {
    			Map<String, SchedulingConfigurer> beans =
    					((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
    			List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
    			AnnotationAwareOrderComparator.sort(configurers);
    			for (SchedulingConfigurer configurer : configurers) {
    				configurer.configureTasks(this.registrar);
    			}
    		}
     
    		if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
    			Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
    			try {
    				// Search for TaskScheduler bean...
    				this.registrar.setTaskScheduler(resolveSchedulerBean(beanFactory, TaskScheduler.class, false));
    			}
    			catch (NoUniqueBeanDefinitionException ex) {
    				logger.debug("Could not find unique TaskScheduler bean", ex);
    				try {
    					this.registrar.setTaskScheduler(resolveSchedulerBean(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) {
    				logger.debug("Could not find default TaskScheduler bean", ex);
    				// Search for ScheduledExecutorService bean next...
    				try {
    					this.registrar.setScheduler(resolveSchedulerBean(beanFactory, ScheduledExecutorService.class, false));
    				}
    				catch (NoUniqueBeanDefinitionException ex2) {
    					logger.debug("Could not find unique ScheduledExecutorService bean", ex2);
    					try {
    						this.registrar.setScheduler(resolveSchedulerBean(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) {
    					logger.debug("Could not find default ScheduledExecutorService bean", ex2);
    					// Giving up -> falling back to default scheduler within the registrar...
    					logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
    				}
    			}
    		}
     
    		this.registrar.afterPropertiesSet();
    	}
    
    1. 找到SchedulingConfigurer接口的所有所有实现类,调用它的configureTasks方法

      通常我们可以自定义一个类实现SchedulingConfigurer,并对registrar里面的属性赋值,最常用的做法是自定线程池注入到registrar中,因为registrar默认是单线程的线程池,也即是说@Scheduled方法是串行的

    2. 对ScheduledTaskRegistrar对像中的TaskScheduler属性赋值

      TaskScheduler这个属性即是线程池对像,如果在第一步我们已经自己指定了一个线程池,这部分代码会跳过。

    3. 调用this.registrar.afterPropertiesSet()

      这一步很重要,它的功能就是把所有任务提交到线程池中。

      @Override
      	public void afterPropertiesSet() {
      		scheduleTasks();
      	}
       
      	/**
      	 * Schedule all registered tasks against the underlying
      	 * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
      	 */
      	@SuppressWarnings("deprecation")
      	protected void scheduleTasks() {
      		if (this.taskScheduler == null) {
      			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
      			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));
      			}
      		}
      	}
      

      ScheduledThreadPoolExecutor的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);
          }
      }
      
      1. 首先判断线程池是否为空,如果是空,就新建一个单线程的线程池
      2. 先执行corn,计算出相应的下次执行时间,放入线程池中。再执行fixedRate,计算出相应的下次执行时间,放入线程池中。
      3. 线程池中某一任务到达执行时间,开始执行,执行完成后会先设置下次定时任务的执行时间,放到线程池中。

    到这一步,@Scheduled作用生效,方法将会定时执行了。

    定时任务线程池执行原理

    定时任务使用的一般是ScheduledThreadPoolExecutor线程池
    该类型线程池解析见该文章:Java线程池解析

    注意事项

    1. 任务默认是单线程执行,前一个任务执行完毕,才会继续执行下一个任务。所以上一个任务一直未完成,会导致后面的任务全部“失效”。
    2. 如果多个定时任务定义的是同一个时间,那么也是顺序执行的,会根据程序加载Scheduled方法的先后来执行。先执行cron,之后再执行fixedRate。
    展开全文
  • 标签:archiva-scheduled-1.0-javadoc.jar,archiva,scheduled,1.0,javadoc,jar包下载,依赖包
  • Spring 原理之 @Scheduled 前言 在开发过程中,我们会用一些简单的定时任务来实现操作,例如定时去捞取流水重试业务、定时去消息中间件获取消息等等相关需求 简单的定时任务实现可以借助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
      请添加图片描述

    在后置处理器核心方法就是在 初始化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();
    }
    
    展开全文
  • SpringBoot定时任务@Scheduled注解详解

    万次阅读 多人点赞 2021-03-01 19:53:20
    SpringBoot定时任务@Scheduled注解详解 项目开发中,经常会遇到定时任务的场景,Spring提供了@Scheduled注解,方便进行定时任务的开发 概述 要使用@Scheduled注解,首先需要在启动类添加@EnableScheduling,启用...

    SpringBoot定时任务@Scheduled注解详解

    项目开发中,经常会遇到定时任务的场景,Spring提供了@Scheduled注解,方便进行定时任务的开发

    概述

    要使用@Scheduled注解,首先需要在启动类添加@EnableScheduling,启用Spring的计划任务执行功能,这样可以在容器中的任何Spring管理的bean上检测@Scheduled注解,执行计划任务

    注解定义

    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Repeatable(Schedules.class)
    public @interface Scheduled {
    
    	String cron() default "";
    
    	String zone() default "";
    
    	long fixedDelay() default -1;
    
    	String fixedDelayString() default "";
    
    	long fixedRate() default -1;
    
    	String fixedRateString() default "";
    	
    	long initialDelay() default -1;
    
    	String initialDelayString() default "";
    
    }
    

    参数说明

    参数参数说明示例
    cron任务执行的cron表达式0/1 * * * * ?
    zonecron表达时解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析GMT-8:00
    fixedDelay上一次任务执行结束到下一次执行开始的间隔时间,单位为ms1000
    fixedDelayString上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析PT15M
    fixedRate以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务2000
    fixedRateString与fixedRate逻辑一致,只是使用java.time.Duration#parse解析PT15M
    initialDelay首次任务执行的延迟时间1000
    initialDelayString首次任务执行的延迟时间,使用java.time.Duration#parse解析PT15M

    源码解析

    配置了@Scheduled注解的方法,Spring的处理是通过注册ScheduledAnnotationBeanPostProcessor来执行,将不同配置参数的任务分配给不同的handler处理,核心代码如下

    • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
    	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);
    			}
    		}
    		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;
    }
    
    • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled
    /**
     * Process the given {@code @Scheduled} method declaration on the given bean.
     * @param scheduled the @Scheduled annotation
     * @param method the method that the annotation has been declared on
     * @param bean the target bean instance
     * @see #createRunnable(Object, Method)
     */
    protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    	try {
    		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);
    		// Determine initial delay
    		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");
    				}
    			}
    		}
    		// Check cron expression
    		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))));
    				}
    			}
    		}
    		// At this point we don't need to differentiate between initial delay set or not anymore
    		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);
    		// Finally register the scheduled tasks
    		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());
    	}
    }
    
    • org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
    /**
     * Schedule all registered tasks against the underlying
     * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
     */
    proected void scheduleTasks() {
    	if (this.taskScheduler == null) {
    		this.localExecutor = Executors.newSingleThreadScheduledExecutor();
    		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));
    		}
    	}
    }
    

    使用详解

    定时任务同步/异步执行

    定时任务执行默认是单线程模式,会创建一个本地线程池,线程池大小为1。当项目中有多个定时任务时,任务之间会相互等待,同步执行
    源码:

    // org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
    if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }
    
    // java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
    

    代码示例:

    @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest1() {
            log.info("singleThreadTest1");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest2() {
            log.info("singleThreadTest2");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest3() {
            log.info("singleThreadTest3");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    }
    

    执行结果:
    在这里插入图片描述

    可以看到,默认情况下,三个任务串行执行,都使用pool-1-thread-1同一个线程池,并且线程只有一个

    可以通过实现SchedulingConfigurer接口,手动创建线程池,配置期望的线程数量

    示例代码:

    @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();
            scheduledTaskRegistrar.setTaskScheduler(taskPool);
        }
    }
    

    任务执行结果:

    在这里插入图片描述

    此时任务的执行已经异步化,从自定义线程池中分配线程执行任务,在实际应用中需要考虑实际任务数量,创建相应大小的线程池

    fixedRate/fixedDelay区别

    • fixedRate是配置上一次任务执行开始到下一次执行开始的间隔时间,不会等待上一次任务执行完成就会调度下一次任务,将其放入等待队列中

    代码示例:

    @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    
        @Scheduled(initialDelay = 1000, fixedRate = 1000)
        public void fixedRate() throws Exception {
            log.info("fixedRate run");
            TimeUnit.SECONDS.sleep(3);
        }
    
    }
    

    执行结果:

    在这里插入图片描述

    任务配置的fixedRate为1s,执行日志打印的时间间隔都是3s左右,也就是上一次执行完成后,紧接着就执行下一次任务

    • fixedDelay是配置的上一次任务执行结束到下一次执行开始的间隔时间,也就是说会等待上一次任务执行结束后,延迟间隔时间,再执行下一次任务

    代码示例:

    @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    
        @Scheduled(initialDelay = 1000, fixedDelay = 1000)
        public void fixedDelay() throws Exception {
            log.info("fixedDelay run");
            TimeUnit.SECONDS.sleep(3);
        }
    
    }
    

    执行结果:

    在这里插入图片描述

    任务配置的fixedDelay为1s,执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

    • cron表达式如果配置为类似每秒执行、每分钟执行(例:0/1 * * * * ?, 每秒执行),调度跟fixedDelay是一致的,也是在上一次任务执行结束后,等待间隔时间

    代码示例:

    @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    
        @Scheduled(cron = "0/1 * * * * ?")
        public void cronRun() throws Exception{
            log.info("cron run");
            TimeUnit.SECONDS.sleep(3);
        }
    
    }
    

    执行结果:

    在这里插入图片描述

    执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

    • cron表达式如果配置为固定时间执行(例:1 * * * * ?, 秒数为1时执行),若上一次任务没有执行完,则不会调度本次任务,跳过本次执行,等待下一次执行周期

    代码示例:

    @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    
        @Scheduled(cron = "1 * * * * ?")
        public void cronRun() throws Exception{
            log.info("cron run");
            TimeUnit.SECONDS.sleep(70);
        }
    
    }
    

    执行结果:

    在这里插入图片描述

    上一次任务未执行完毕,则跳过了本次执行

    展开全文
  • Scheduled动态定时器

    2021-11-01 17:51:02
    * 1#1 每个月的第一个周一凌晨2点到4点期间,每个整点都执行一次 */ @Scheduled(cron = "0 * * * * MON-FRI") @Scheduled(cron = "0,1,2,3,4 * * * * MON-FRI") @Scheduled(cron = "0-4 * * * * MON-FRI") @...
  • scheduled-scaler

    2021-05-05 15:40:52
    预定洁牙机 为了使用ScheduledScaler,您将需要安装CRD并将Scaling ...git clone https://github.com/k8s-restdev/scheduled-scaler.git $GOPATH/src/k8s.restdev.com/operators && \ cd $GOPATH/src/k8s.restdev
  • 在一次SpringBoot中使用Scheduled定时任务时,发现某一个任务出现执行占用大量资源,会导致其他任务也执行失败。 类似于以下模拟场景,test1定时任务模拟有五秒钟执行时间,这时会同步影响到test2任务的执行,导致...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 77,450
精华内容 30,980
关键字:

scheduled

友情链接: alllist.rar