SpringBoot,设置并发执行定时任务Scheduled(分析EnableScheduling ,ScheduledAnnotationBeanPostProcessor)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a718515028/article/details/80396102

前言:在springboot默认的线程池中,是单一线程。所以默认情况下,所有Scheduled不能并发执行。
这里简单的写了三个方案写法
解决方法都是自定义一个线程池,
一般通常的写法是下面这种,重写SchedulingConfigurer ,使用自定义的Scheduled
方案一

@Configuration
public class TestConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(TaskScheduler());
    }
    @Bean(destroyMethod = "shutdown")
    public Executor TaskScheduler() {
        return Executors.newScheduledThreadPool(10);
    }
}

但是,在使用期间发现,即使不重写SchedulingConfigurer ,如下代码,依然可以成功的并发执行,
方案二

@Configuration
public class TestConfiguration {

    @Bean(destroyMethod = "shutdown")
    public Executor TaskScheduler() {
        return Executors.newScheduledThreadPool(10);
    }
}

查了很多资料,都没有关于SchedulingConfigurer是否必须重写的区别。只能从EnableScheduling 注解开始翻看源码,进入EnableScheduling 注解,可以发现

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

}

可以看见其中引入了一个SchedulingConfiguration,继续进入SchedulingConfiguration.class,最终来到ScheduledAnnotationBeanPostProcessor.class。
在springboot项目启动时,通过EnableScheduling注解,最终来到ScheduledAnnotationBeanPostProcessor这个类,通过其中方法private void finishRegistration(),开始加载加载自定义的线程池。

 private void finishRegistration() {
        if (this.scheduler != null) {
            this.registrar.setScheduler(this.scheduler);
        }

        // 1 --- 查找是否有SchedulingConfigurer类型的自定义的bean 
        if (this.beanFactory instanceof ListableBeanFactory) {
            Map<String, SchedulingConfigurer> configurers =
                    ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
            for (SchedulingConfigurer configurer : configurers.values()) {
                //1.1 、使用configureTasks重写的方法,为this.registrar赋值,初始化taskScheduler
                configurer.configureTasks(this.registrar);
            }
        }

        // 2  ---如果上面 1.1中已经赋值的话,这里this.registrar.getScheduler()!=null,直接跳过
        if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
            Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
            try {
                // 2.1---  查找TaskScheduler类型的bean ,false表示不使用名字查找,根据结果初始化taskScheduler
                this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));
            }
            catch (NoUniqueBeanDefinitionException ex) {
                //2.2 --- 注意异常名字,NoUniqueBeanDefinitionException,不唯一
                //如果有多个TaskScheduler类型的bean,则使用名字“taskScheduler”查找,根据结果初始化taskScheduler
                try {
                    this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, true));
                }
                catch (NoSuchBeanDefinitionException ex2) {
                    //2.3 ---有多个TaskScheduler类型的bean,且,查询不到name为“taskScheduler”的bean
                    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) {
                //2.4---如果查不到TaskScheduler类型的bean,则默认查询ScheduledExecutorService类型的bean。根据结果初始化taskScheduler
                logger.debug("Could not find default TaskScheduler bean", ex);
                // Search for ScheduledExecutorService bean next...
                try {
                    this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, false));
                }
                catch (NoUniqueBeanDefinitionException ex2) {
                    //2.5 --- 如果不唯一,则根据名字taskScheduler查询。根据结果初始化taskScheduler
                    try {
                        this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, true));
                    }
                    catch (NoSuchBeanDefinitionException ex3) {
                        //2.6 ---如果不唯一,且根据名字也查不到
                        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) {
                    //2.7---如果查询不到,ScheduledExecutorService类型的bean
                    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");
                }
            }
        }

//2.8 
        this.registrar.afterPropertiesSet();
    }

进入代码最后2.8中的afterPropertiesSet方法:

    @Override
    public void afterPropertiesSet() {
        scheduleTasks();
    }
    protected void scheduleTasks() {
    //如果taskScheduler始终未被初始化,则使用默认的线程池,newSingleThreadScheduledExecutor(),看名字就知道是单线程
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        ......

结论,
(一)根据上面代码注释代号,“注释代号”1,解释了文章的方案一,启动时查找SchedulingConfigurer,并通过重写的方法configureTasks ,显式的指定Scheduler,初始化线程池。

(二)在上面注释,2.4到2.7中,解释了,若Scheduler一直没有初始化,则会默认查询ScheduledExecutorService 这个bean,这就解释了,文章开始说的方案二,其中直接返回了一个“return Executors.newScheduledThreadPool(10);”即,ScheduledExecutorService类型。所以,会加载此bean,初始化线程池。
所以,即使不重写SchedulingConfigurer 这个接口,加载器依然能找到需要加载的线程池。

意外收获:
在看源码过程中,发现上面2.1 到2.3中,提供了另一种加载方式,在没有指定SchedulingConfigurer时,则会优先查询 TaskScheduler类型的bean,然后再ScheduledExecutorService 。
所以也可以通过定义TaskScheduler 实现多线程并发定时任务,简单的写法如下

方案三

@Configuration
public class TestConfiguration  {
    @Bean
    public TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        taskScheduler.initialize();
        return taskScheduler;
    }
}

从这里可以看出加载顺序,SchedulingConfigurer –> TaskScheduler –>ScheduledExecutorService
TaskScheduler 和ScheduledExecutorService 会优先根据类型找,如果存在多个同类型的,则根据默认的名字“taskScheduler”来找

所以,通常会显式的重写SchedulingConfigurer 接口,这样就不会再继续查找后面的TaskScheduler和ScheduledExecutorService了(看注释代号2)

附task

@Service
public class Task {
   //5秒一次
    @Scheduled(cron = "*/5 * * * * ?")
    public void task1() throws InterruptedException {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "    " + Thread.currentThread().getName() + "    任务一启动");
        Thread.sleep(10000);//任务耗时10秒
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "    " + Thread.currentThread().getName() + "    结束");

    }

    @Scheduled(cron = "*/5 * * * * ?")
    public void task2() throws InterruptedException {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "    " + Thread.currentThread().getName() + "    任务二启动");
        Thread.sleep(10000);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "    " + Thread.currentThread().getName() + "    结束");
    }
}

@SpringBootApplication
@EnableScheduling
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class,args);
    }
}

测试结果:5秒一次,执行耗时10秒
可以看出,两个任务可以同时执行,同一个任务不会并发执行(对于同一个任务,上一个执行完后,再进行下一次任务,可以看出两个任务都是过了10秒执行完后,等待5秒再次执行,而不是固定的5秒一次)
这里写图片描述

猜你喜欢

转载自blog.csdn.net/a718515028/article/details/80396102
今日推荐