spring schedule定时任务详解
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;
}
- 先创建执行runnable
- 获取延迟执行时间
- 获取cron表达式,创建CronTask,registrar中添加任务
- 获取固定延迟,创建FixedDelayTask,registrar中添加任务
- 获取固定执行间隔,创建FixedRateTask,registrar中添加任务
- 把所有任务都添加到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实现类
- ScheduledAnnotationBeanPostProcessor实现了ApplicationListener接口,当工程启动好后调用onApplicationEvent方法,执行finishRegistration方式。
- 扫描所有的SchedulingConfigurer实现类,调用configureTasks回调函数添加定时任务。
- 调用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、添加定时任务到线程池
- afterPropertiesSet实际是在调用scheduleTasks方法安排任务
- 判断任务执行器是否存在,如果不存在则使用Executors.newSingleThreadScheduledExecutor()
- 判断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进行分析,其他任务类似:
- 首先从未解决的任务里面获取并移除当前任务
- 如果为空,则创建新的任务;从scheduleTasks()方法进来的时候已经存在任务
- 如果存在任务执行器,则调用方法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定时任务详解相关推荐
- Spring整合Schedule定时任务详解
Spring整合Schedule定时任务详解 Spring 定时任务官方网站 一.概述 用Spring,就是为了简单. 但是我还是要总结下java定时任务实现的几种方式. 1.TimerTask,等于 ...
- navicat创建MySql定时任务详解
目录 navicat创建MySql 定时任务详解 一.开起定时任务 二.通过navicat创建定时任务 三.关闭.启动.别名.移动.删除event 四.查询Event信息 navicat创建MySql ...
- Spring JdbcTemplate方法详解
2019独角兽企业重金招聘Python工程师标准>>> Spring JdbcTemplate方法详解 标签: springhsqldbjava存储数据库相关sql 2012-07- ...
- Spring Boot 配置文件详解
2019独角兽企业重金招聘Python工程师标准>>> 第二篇 : Spring Boot配置文件详解 文章首发于微信公众号<程序员果果> 地址:https://mp.w ...
- spring2.0和spring2.5及以上版本的jar包区别 spring jar 包详解
spring jar 包详解 spring.jar是包含有完整发布的单个jar包,spring.jar中包含除了 spring-mock.jar里所包含的内容外其它所有jar包的内容,因为只有在开发环 ...
- Spring的lazy-init详解
Spring中lazy-init详解 ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化 (也就是依赖注入).提前实例化意味着作为初始 ...
- spring boot配置文件详解
spring boot配置文件详解 application.properties是spring-boot的核心配置文件,这个配置文件基本可以取代我们ssm或者ssh里面的所有的xml配置文件. 当我们 ...
- 一文搞定 Spring Data Redis 详解及实战
转载自 一文搞定 Spring Data Redis 详解及实战 SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问 ...
- Spring Boot 单元测试详解+实战教程
转载自 Spring Boot 单元测试详解+实战教程 Spring Boot 的测试类库 Spring Boot 提供了许多实用工具和注解来帮助测试应用程序,主要包括以下两个模块. spring ...
最新文章
- html5面板制作代码,HTML5绘制设备面板
- UI设计培训教程分享:UI设计师的色彩使用技巧
- 浙大计算机科学基础题型,浙江大学878计算机学科专业基础(含数据结构)考研复习经验...
- 故障模块名称kernelbase.dll_TLY-01L12/16宜宾智能照明调光模块
- PHP源码设置超出隐藏,怎样隐藏文本的超出部分
- ansible介绍+基本操作
- WVI职业价值观测量表
- oracle字段去重查询,oracle怎么去重查询
- 阿里巴巴港股股价创历史新高 市值超6.1万亿港元
- 382 名员工遍布 47 个国家如何炼成代码托管平台 GitLab?
- HDU1280 前m大的数【排序】
- Mac 上管理多个 java 版本
- 热力地图高德_HeatMap丨丨基于高德地图API制作热力图。
- 用java代码编写出喜字_喜字是怎么写的
- C++基础知识 - 浮点类型
- mscorsvw.exe 关闭方法
- iOS APP 运行时防Crash工具XXShield练就
- 老调重弹,Squirrel,FASTCGI
- 今年大火的元宇宙,到底是什么?
- 电信联通“抱团”,资费有望降低
热门文章
- 【AI简报20220211期】硬核UP主自己造了一个激光雷达、详解AI加速器
- 为什么要将线程设置成分离状态
- vasp 真空能级 matlab,若干表面体系的第一性原理研究
- KindEditor编辑器的使用
- PHPMyadmin
- arcmap 连接天地图
- 随机循环抽奖小程序_c语言,基于JavaScript实现简单的随机抽奖小程序
- 面试题总结(mybatis一级缓存及二级缓存、springboot自动装配原理等)
- php输出指定日期,PHP 输出两个指定日期之间的所有日期
- c# 没有注册类别 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))