简介
html
以前使用@Schedule一直没有遇到什么问题,那种拿来就用的感受还挺好,最近使用@Schedule遇到一点问题,才仔细的研究了一下@Schedule的一些细节和原理问题。java
这篇文章就将分享一下,使用@Schedule一些可能被忽略的问题。spring
我相信@Schedule默认线程池大小的问题确定是被不少拿来就用的朋友忽略的问题,默认状况下@Schedule使用线程池的大小为1。app
通常状况下没有什么问题,可是若是有多个定时任务,每一个定时任务执行时间可能不短的状况下,那么有的定时任务可能一直没有机会执行。ide
有兴趣的朋友,能够试一下:post
@Component public class BrigeTask { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Scheduled(cron = "*/5 * * * * ?") private void cron() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-cron:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } @Scheduled(fixedDelay = 5000) private void fixedDelay() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-fixedDelay:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } @Scheduled(fixedRate = 5000) private void fixedRate() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-fixedRate:" + LocalDateTime.now().format(FORMATTER)); TimeUnit.SECONDS.sleep(6); } }
上面的任务中,fixedDelay与cron,可能好久都不会被执行。测试
要解决上面的问题,能够把执行任务的线程池设置大一点,怎样设置经过实现SchedulingConfigurer接口,在configureTasks方法中配置,这种方式参见后面的代码,这里能够直接注入一个TaskScheduler来解决问题。this
@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(5); return taskScheduler; }
固然也能够使用ScheduledExecutorService:spa
@Bean public ScheduledExecutorService scheduledExecutorService() { return Executors.newScheduledThreadPool(10); }
为啥这样有效,请参考后面@Schedule原理。线程
@Schedule的三种方式cron、fixedDelay、fixedRate无论线程够不够都会阻塞到上一次执行完成,才会执行下一次。
若是任务方法执行时间很是短,上面三种方式其实基本没有太多的区别。
若是,任务方法执行时间比较长,大于了设置的执行周期,那么就有很大的区别。例如,假设执行任务的线程足够,执行周期是5s,任务方法会执行6s。
cron的执行方式是,任务方法执行完,遇到下一次匹配的时间再次执行,基本就会10s执行一次,由于执行任务方法的时间区间会错过一次匹配。
fixedDelay的执行方式是,方法执行了6s,而后会再等5s再执行下一次,在上面的条件下,基本就是每11s执行一次。
fixedRate的执行方式就变成了每隔6s执行一次,由于按固定区间执行它没5s就应该执行一次,可是任务方法执行了6s,没办法,只好6s执行一次。
上面的结论均可以经过,最上面的示例验证,有兴趣的朋友能够调整一下休眠时间测试一下。
在SpringBoot中,咱们使用@EnableScheduling来启用@Schedule。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }
EnableScheduling注解没什么特殊,须要注意import了SchedulingConfiguration。
SchedulingConfiguration一看名字就知道是一个配置类,确定是为了添加相应的依赖类。
@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(); } }
咱们能够看到在SchedulingConfiguration建立了一个ScheduledAnnotationBeanPostProcessor。
看样子SpringBoot定时任务的核心就是ScheduledAnnotationBeanPostProcessor类了,下面咱们来看一看ScheduledAnnotationBeanPostProcessor类。
public class ScheduledAnnotationBeanPostProcessor implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware, SmartInitializingSingleton, ApplicationListener, DisposableBean { }
ScheduledAnnotationBeanPostProcessor实现了不少接口,这里重点关注2个,ApplicationListener和DestructionAwareBeanPostProcessor。
DestructionAwareBeanPostProcessor继承了BeanPostProcessor。
BeanPostProcessor相信你们已经很是熟悉了,就是在Bean建立执行setter以后,在自定义的afterPropertiesSet和init-method先后提供拦截点,大体执行的前后顺序是:
Bean实例化 -> setter -> BeanPostProcessor#postProcessBeforeInitialization ->
-> InitializingBean#afterPropertiesSet -> init-method -> BeanPostProcessor#postProcessAfterInitialization
咱们看一下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> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set>) method -> { SetscheduledMethods = 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; }
简单说一下流程:
找到全部的Schedule方法,把它封装为ScheduledMethodRunnable类(ScheduledMethodRunnable类实现了Runnable接口),并把其作为一个任务注册到ScheduledTaskRegistrar中。
若是对具体的逻辑感兴趣,能够从postProcessAfterInitialization方法顺着processScheduled方法一次debug。
前面咱们介绍经过BeanPostProcessor解析出了全部的任务,接下来要作的事情就是提交任务了。
@Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.applicationContext) { // Running in an ApplicationContext -> register tasks this late... // giving other ContextRefreshedEvent listeners a chance to perform // their work at the same time (e.g. Spring Batch's job registration). finishRegistration(); } }
ScheduledAnnotationBeanPostProcessor监听的事件是ContextRefreshedEvent,就是在容器初始化,或者刷新的时候被调用。
监听到ContextRefreshedEvent事件以后,值调用了finishRegistration方法,这个方法的基本流程以下:
import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; @Configuration public class MyScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }
关于为啥直接在容器中注入一个TaskScheduler、ScheduledExecutorService也能够有效,也能够在finishRegistration方法中找到答案。