一文教你实现Spring动态启停定时任务( 二 )

示例:
public class JavaScheduledThreadPoolExecutor {public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(8);//延时1秒后开始执行 , 每3秒执行一次scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println(new Date()+": This is my job...");}}, 1, 3, TimeUnit.SECONDS);}}执行结果:
Tue May 30 15:05:16 CST 2022: This is my job...Tue May 30 15:05:19 CST 2022: This is my job...Tue May 30 15:05:22 CST 2022: This is my job...Tue May 30 15:05:25 CST 2022: This is my job... 。。。。。Timer VS ScheduledThreadPoolExecutorTimer

  • 是单线程 , 如果开启多个线程服务 , 将会出现竞争 , 一旦出现异常 , 线程停止 , 定时任务停止;
  • 兼容性更高 , jdk1.3后使用
ScheduledThreadPoolExecutor
  • 基于线程池实现多线程 , 且自动调整线程数 , 线程出错并不会影响整体定时任务执行 。
  • 在jdk1.5后可使用
Spring定时任务Spring原生定时任务主要依靠@Scheduled注解实现:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Repeatable(Schedules.class)public @interface Scheduled {String CRON_DISABLED = "-";String cron() default "";//类似于corn表达式 , 可以指定定时任务执行的延迟及周期规则String zone() default "";//指明解析cron表达式的时区 。long fixedDelay() default -1;//在最后一次调用结束和下一次调用开始之间以固定周期(以毫秒为单位)执行带注解的方法 。(要等待上次任务完成后)String fixedDelayString() default "";//同上面作用一样 , 只是String类型long fixedRate() default -1;//在调用之间以固定的周期(以毫秒为单位)执行带注解的方法 。(不需要等待上次任务完成)String fixedRateString() default "";//同上面作用一样 , 只是String类型long initialDelay() default -1;//第一次执行fixedRate()或fixedDelay()任务之前延迟的毫秒数。String initialDelayString() default "";//同上面作用一样 , 只是String类型}Spring静态定时任务示例:
@Slf4j@Componentpublic class TestJob {//每40秒执行一次@Scheduled(cron = "0/40 * * * * ?")public void logJob(){if(log.isDebugEnabled()){log.debug("现在是:{}",LocalDateTime.now());}}}执行结果:
现在是:2022-05-30T16:03:40.006现在是:2022-05-30T16:04现在是:2022-05-30T16:04:40.003@Scheduled定时任务原理(源码)①项目启动扫描带有注解@Scheduled的所有方法信息由
ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法实现功能:
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)) {//获取定时任务的方法Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledMethods = 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 methodsannotatedMethods.forEach((method, scheduledMethods) ->//调用processScheduled方法将定时任务方法存放到任务队列中scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isTraceEnabled()) {logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}}return bean;}②调用processScheduled方法将定时任务方法存放到任务队列中
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);//解析任务执行初始延迟long 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");}}}//解析cron表达式String 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;}//解析fixedDelay参数long 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)));}}//解析fixedRate参数long 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)));}}// 断言检查Assert.isTrue(processedSchedule, errorMessage);//并发控制将任务队列存入注册任务列表synchronized (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());}}


推荐阅读