为防止打脸,先写明版本:spring boot 2.2.2.RELEASE
spring boot 中自带了一个轻量级的任务调度框架,使用也非常简单。
@EnableScheduling
,当然需要放在一个可以被扫描到的类上,比如启动类、使用了 @Configuration
的配置类。当然你要放在一个 @Component
的类上除了不太规范,我也无话可说。 在需要定时运行的方法上加上 @Scheduled
注解,并设置调度方式,支持
就这么简单。
现在有两个任务A和B
任务A在5点执行,并耗时2个小时
@Scheduled(cron = "0 0 5 * * *") public void taskA() throws InterruptedException { log.info("taskA running"); Thread.sleep(2 * 60 * 60 * 1000);//模拟任务耗时,2个小时 }
任务B在6点执行
@Scheduled(cron = "0 0 6 * * *") public void taskB() { log.info("taskB running"); }
灵魂一问:任务B能按预期在6点执行吗?
如果觉得能正常执行,怕是已经忘记了标题。
先说结论:任务B不能在6点执行,因为调度器是的线程池大小为1。
不用想,肯定是spring boot 自动配置的。
先看注解 @EnableScheduling
,注释中有写到
By default, will be searching for an associated scheduler definition: either a unique org.springframework.scheduling.TaskScheduler bean in the context, or a TaskScheduler bean named "taskScheduler" otherwise; the same lookup will also be performed for a java.util.concurrent.ScheduledExecutorService bean. If neither of the two is resolvable, a local single-threaded default scheduler will be created and used within the registrar. When more control is desired, a @Configuration class may implement SchedulingConfigurer. This allows access to the underlying ScheduledTaskRegistrar instance.
整理下,大概意思是,默认情况下,会寻找一个调度器,按照以下顺序
org.springframework.scheduling.TaskScheduler
类型的bean taskScheduler
的 org.springframework.scheduling.TaskScheduler
类型的bean java.util.concurrent.ScheduledExecutorService
类型的bean taskScheduler
的 java.util.concurrent.ScheduledExecutorService
类型的bean 如果想掌控更多,可以写一个实现了 SchedulingConfigurer
接口的配置类。 如果在这个配置类设置了调度器,就不会再寻找了 。
看上去应该是走到了第5种情况。
@EnableScheduling
引入了 SchedulingConfiguration
, SchedulingConfiguration
中定义了一个 ScheduledAnnotationBeanPostProcessor
类型的bean。
@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(); } }
设置调度器的代码在 finishRegistration
方法中。
跟代码发现其实是在执行第一种情况时就已经满足了。
这个bean是那里定义的?
一(全)番(凭)摸(运)索(气),找到了一个自动配置类 TaskSchedulingAutoConfiguration
,其中定义了 ThreadPoolTaskScheduler
类型的bean。
看一下上面的条件
org.springframework.context.annotation.internalScheduledAnnotationProcessor
的bean才加载,而这个名称就是 SchedulingConfiguration
中定义 ScheduledAnnotationBeanPostProcessor
的名称。 SchedulingConfigurer
、 TaskScheduler
、 ScheduledExecutorService
这些类型的bean时才加载。这也是为了保持扩展性。开发者定义了相关的bean,框架就不自动配置了。 @Bean @ConditionalOnBean( name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"} ) @ConditionalOnMissingBean({SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class}) public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { return builder.build(); }
那么如何修改调度器线程池大小呢
在 TaskSchedulingAutoConfiguration
类中定义了上面方法需要的 TaskSchedulerBuilder
@Bean @ConditionalOnMissingBean public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) { TaskSchedulerBuilder builder = new TaskSchedulerBuilder(); builder = builder.poolSize(properties.getPool().getSize()); Shutdown shutdown = properties.getShutdown(); builder = builder.awaitTermination(shutdown.isAwaitTermination()); builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); builder = builder.customizers(taskSchedulerCustomizers); return builder; }
可以看到线程池大小是通过读配置设置的,也就是设置 spring.task.scheduling.pool.size
。
当然上述方法是沿用spring boot 自动配置的,你也可以自己定义,只要搞清楚寻找的优先级就没问题了。
看到了这里一定是真爱了,关注微信公众号【憨憨的春天】第一时间获取更新