spring schedule定时任务

文章目录

  • spring schedule定时任务
    • 一、如何使用定时任务
      • 1、启动类使用@EnableScheduling注解开启定时任务
      • 2、方法使用@Scheduled注解,或者实现SchedulingConfigurer接口,添加定时任务
    • 二、配置定时任务多线程非阻塞运行
      • 1、阻塞原因
      • 2、如何解决,实现SchedulingConfigurer接口,设置任务调度器实现类
    • 三、源码解析
      • 1、@EnableScheduling注解启用定时任务
      • 2、扫描@Scheduled、@Schedules注解
      • 3、扫描SchedulingConfigurer实现类
      • 4、添加定时任务到线程池

一、如何使用定时任务

1、启动类使用@EnableScheduling注解开启定时任务

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

2、方法使用@Scheduled注解,或者实现SchedulingConfigurer接口,添加定时任务

@Scheduled(cron = "0/2 * * * * ? ")
public void index1() {log.info("定时任务1,2秒执行一次,time:" + DateTime.now() + " 线程:" + Thread.currentThread().getName());
}
@Configuration
@Component
@Slf4j
public class TestTask implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.addFixedDelayTask(this::index3, 1000);}public void index2() {log.info("定时任务2,1秒执行一次,time:" + DateTime.now() + " 线程:" + Thread.currentThread().getName());}}

二、配置定时任务多线程非阻塞运行

1、阻塞原因

默认情况,定时任务使用的是单例线程执行器Executors.newSingleThreadScheduledExecutor(),所以当一个定时任务阻塞是,所有定时任务都不会执行:

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {@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));}}}
}

2、如何解决,实现SchedulingConfigurer接口,设置任务调度器实现类

使用线程池执行定时任务,ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

@Configuration
@Component
@Slf4j
public class TestTask implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadFactory nameThreadFactory = new ThreadFactoryBuilder().setNameFormat("scheduled-%d").build();taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(5,nameThreadFactory,new ThreadPoolExecutor.AbortPolicy()));}
}

三、源码解析

1、@EnableScheduling注解启用定时任务

打开注解,发现这里只是在引用SchedulingConfiguration.class配置

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

打开配置类,发现是在实例化ScheduledAnnotationBeanPostProcessor类

@Configuration(proxyBeanMethods = false)
@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();}}

注解Bean后置处理器初始化方法是在实例化ScheduledTaskRegistrar类

public class ScheduledAnnotationBeanPostProcessorimplements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {/*** The default name of the {@link TaskScheduler} bean to pick up: {@value}.* <p>Note that the initial lookup happens by type; this is just the fallback* in case of multiple scheduler beans found in the context.* @since 4.2*/public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";protected final Log logger = LogFactory.getLog(getClass());private final ScheduledTaskRegistrar registrar;@Nullableprivate Object scheduler;private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);/*** Create a default {@code ScheduledAnnotationBeanPostProcessor}.*/public ScheduledAnnotationBeanPostProcessor() {this.registrar = new ScheduledTaskRegistrar();}
}

2、扫描@Scheduled、@Schedules注解

ScheduledAnnotationBeanPostProcessor实现了BeanPostProcessor接口。调用postProcessAfterInitialization后置处理器扫描注解,全部转换为Scheduled后,调用processScheduled方法

@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> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : 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 methodsannotatedMethods.forEach((method, scheduledAnnotations) ->scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isTraceEnabled()) {logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}}return bean;
}
  1. 先创建执行runnable
  2. 获取延迟执行时间
  3. 获取cron表达式,创建CronTask,registrar中添加任务
  4. 获取固定延迟,创建FixedDelayTask,registrar中添加任务
  5. 获取固定执行间隔,创建FixedRateTask,registrar中添加任务
  6. 把所有任务都添加到scheduledTasks
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 delaylong 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 expressionString 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 anymoreif (initialDelay < 0) {initialDelay = 0;}// Check fixed delaylong 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 ratelong 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 setAssert.isTrue(processedSchedule, errorMessage);// Finally register the scheduled taskssynchronized (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());}
}

3、扫描SchedulingConfigurer实现类

  1. ScheduledAnnotationBeanPostProcessor实现了ApplicationListener接口,当工程启动好后调用onApplicationEvent方法,执行finishRegistration方式。
  2. 扫描所有的SchedulingConfigurer实现类,调用configureTasks回调函数添加定时任务。
  3. 调用registrar 的afterPropertiesSet方法。
@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();}
}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(this.beanFactory, TaskScheduler.class, false));}catch (NoUniqueBeanDefinitionException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +ex.getMessage());}try {this.registrar.setTaskScheduler(resolveSchedulerBean(this.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) {if (logger.isTraceEnabled()) {logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +ex.getMessage());}// Search for ScheduledExecutorService bean next...try {this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));}catch (NoUniqueBeanDefinitionException ex2) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +ex2.getMessage());}try {this.registrar.setScheduler(resolveSchedulerBean(this.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) {if (logger.isTraceEnabled()) {logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +ex2.getMessage());}// Giving up -> falling back to default scheduler within the registrar...logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");}}}this.registrar.afterPropertiesSet();
}

4、添加定时任务到线程池

  1. afterPropertiesSet实际是在调用scheduleTasks方法安排任务
  2. 判断任务执行器是否存在,如果不存在则使用Executors.newSingleThreadScheduledExecutor()
  3. 判断triggerTasks、cronTasks、fixedRateTasks、fixedDelayTasks是否存在,如果存在则addScheduledTask(scheduleTriggerTask(task))添加到taskScheduler,然后添加到scheduledTasks
@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));}}
}

用triggerTasks进行分析,其他任务类似:

  1. 首先从未解决的任务里面获取并移除当前任务
  2. 如果为空,则创建新的任务;从scheduleTasks()方法进来的时候已经存在任务
  3. 如果存在任务执行器,则调用方法taskScheduler.schedule安排任务,并返回一个future执行结果
@Nullable
public ScheduledTask scheduleTriggerTask(TriggerTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask = new ScheduledTask(task);newTask = true;}if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else {addTriggerTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}
private void addScheduledTask(@Nullable ScheduledTask task) {if (task != null) {this.scheduledTasks.add(task);}
}

spring schedule定时任务详解相关推荐

  1. Spring整合Schedule定时任务详解

    Spring整合Schedule定时任务详解 Spring 定时任务官方网站 一.概述 用Spring,就是为了简单. 但是我还是要总结下java定时任务实现的几种方式. 1.TimerTask,等于 ...

  2. navicat创建MySql定时任务详解

    目录 navicat创建MySql 定时任务详解 一.开起定时任务 二.通过navicat创建定时任务 三.关闭.启动.别名.移动.删除event 四.查询Event信息 navicat创建MySql ...

  3. Spring JdbcTemplate方法详解

    2019独角兽企业重金招聘Python工程师标准>>> Spring JdbcTemplate方法详解 标签: springhsqldbjava存储数据库相关sql 2012-07- ...

  4. Spring Boot 配置文件详解

    2019独角兽企业重金招聘Python工程师标准>>> 第二篇 : Spring Boot配置文件详解 文章首发于微信公众号<程序员果果> 地址:https://mp.w ...

  5. spring2.0和spring2.5及以上版本的jar包区别 spring jar 包详解

    spring jar 包详解 spring.jar是包含有完整发布的单个jar包,spring.jar中包含除了 spring-mock.jar里所包含的内容外其它所有jar包的内容,因为只有在开发环 ...

  6. Spring的lazy-init详解

    Spring中lazy-init详解 ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化 (也就是依赖注入).提前实例化意味着作为初始 ...

  7. spring boot配置文件详解

    spring boot配置文件详解 application.properties是spring-boot的核心配置文件,这个配置文件基本可以取代我们ssm或者ssh里面的所有的xml配置文件. 当我们 ...

  8. 一文搞定 Spring Data Redis 详解及实战

    转载自  一文搞定 Spring Data Redis 详解及实战 SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问 ...

  9. Spring Boot 单元测试详解+实战教程

    转载自   Spring Boot 单元测试详解+实战教程 Spring Boot 的测试类库 Spring Boot 提供了许多实用工具和注解来帮助测试应用程序,主要包括以下两个模块. spring ...

最新文章

  1. html5面板制作代码,HTML5绘制设备面板
  2. UI设计培训教程分享:UI设计师的色彩使用技巧
  3. 浙大计算机科学基础题型,浙江大学878计算机学科专业基础(含数据结构)考研复习经验...
  4. 故障模块名称kernelbase.dll_TLY-01L12/16宜宾智能照明调光模块
  5. PHP源码设置超出隐藏,怎样隐藏文本的超出部分
  6. ansible介绍+基本操作
  7. WVI职业价值观测量表
  8. oracle字段去重查询,oracle怎么去重查询
  9. 阿里巴巴港股股价创历史新高 市值超6.1万亿港元
  10. 382 名员工遍布 47 个国家如何炼成代码托管平台 GitLab?
  11. HDU1280 前m大的数【排序】
  12. Mac 上管理多个 java 版本
  13. 热力地图高德_HeatMap丨丨基于高德地图API制作热力图。
  14. 用java代码编写出喜字_喜字是怎么写的
  15. C++基础知识 - 浮点类型
  16. mscorsvw.exe 关闭方法
  17. iOS APP 运行时防Crash工具XXShield练就
  18. 老调重弹,Squirrel,FASTCGI
  19. 今年大火的元宇宙,到底是什么?
  20. 电信联通“抱团”,资费有望降低

热门文章

  1. 【AI简报20220211期】硬核UP主自己造了一个激光雷达、详解AI加速器
  2. 为什么要将线程设置成分离状态
  3. vasp 真空能级 matlab,若干表面体系的第一性原理研究
  4. KindEditor编辑器的使用
  5. PHPMyadmin
  6. arcmap 连接天地图
  7. 随机循环抽奖小程序_c语言,基于JavaScript实现简单的随机抽奖小程序
  8. 面试题总结(mybatis一级缓存及二级缓存、springboot自动装配原理等)
  9. php输出指定日期,PHP 输出两个指定日期之间的所有日期
  10. c# 没有注册类别 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))