欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

我勸你謹(jǐn)慎使用Spring中的@Scheduled注解

 更新時(shí)間:2021年10月18日 09:40:05   作者:慕楓技術(shù)筆記  
這篇文章主要介紹了Spring中的@Scheduled注解使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

引言

在一些業(yè)務(wù)場景中需要執(zhí)行定時(shí)操作來完成一些周期性的任務(wù),比如每隔一周刪除一周前的某些歷史數(shù)據(jù)以及定時(shí)進(jìn)行某項(xiàng)檢測任務(wù)等等。

在日常開發(fā)中比較簡單的實(shí)現(xiàn)方式就是使用Spring的@Scheduled(具體使用方法不再贅述)注解。

但是在修改服務(wù)器時(shí)間時(shí)會(huì)導(dǎo)致定時(shí)任務(wù)不執(zhí)行情況的發(fā)生,解決的辦法是當(dāng)修改服務(wù)器時(shí)間后,將服務(wù)進(jìn)行重啟就可以避免此現(xiàn)象的發(fā)生。

本文將主要探討服務(wù)器時(shí)間修改導(dǎo)致@Scheduled注解失效的原因,同時(shí)找到在修改服務(wù)器時(shí)間后不重啟服務(wù)的情況下,定時(shí)任務(wù)仍然正常執(zhí)行的方法。

  • @Scheduled失效原因分析
  • 解析流程圖
  • 使用新的方法

1.@Scheduled失效原因

(1)首先我們一起看一下@Scheduled注解的源碼,主要說明了注解可使用的參數(shù)形式,在注解中使用了Schedules這個(gè)類。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
 /**
  * A cron-like expression, extending the usual UN*X definition to include
  * triggers on the second as well as minute, hour, day of month, month
  * and day of week.  e.g. {@code "0 * * * * MON-FRI"} means once per minute on
  * weekdays (at the top of the minute - the 0th second).
  * @return an expression that can be parsed to a cron schedule
  * @see org.springframework.scheduling.support.CronSequenceGenerator
  */
 String cron() default "";
 /**
  * A time zone for which the cron expression will be resolved. By default, this
  * attribute is the empty String (i.e. the server's local time zone will be used).
  * @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)},
  * or an empty String to indicate the server's default time zone
  * @since 4.0
  * @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
  * @see java.util.TimeZone
  */
 String zone() default "";
 /**
  * Execute the annotated method with a fixed period in milliseconds between the
  * end of the last invocation and the start of the next.
  * @return the delay in milliseconds
  */
 long fixedDelay() default -1;
 /**
  * Execute the annotated method with a fixed period in milliseconds between the
  * end of the last invocation and the start of the next.
  * @return the delay in milliseconds as a String value, e.g. a placeholder
  * @since 3.2.2
  */
 String fixedDelayString() default "";
 /**
  * Execute the annotated method with a fixed period in milliseconds between
  * invocations.
  * @return the period in milliseconds
  */
 long fixedRate() default -1;
 /**
  * Execute the annotated method with a fixed period in milliseconds between
  * invocations.
  * @return the period in milliseconds as a String value, e.g. a placeholder
  * @since 3.2.2
  */
 String fixedRateString() default "";
 /**
  * Number of milliseconds to delay before the first execution of a
  * {@link #fixedRate()} or {@link #fixedDelay()} task.
  * @return the initial delay in milliseconds
  * @since 3.2
  */
 long initialDelay() default -1;
 /**
  * Number of milliseconds to delay before the first execution of a
  * {@link #fixedRate()} or {@link #fixedDelay()} task.
  * @return the initial delay in milliseconds as a String value, e.g. a placeholder
  * @since 3.2.2
  */
 String initialDelayString() default "";
}

(2)接下來我們來看下,Spring容器是如何解析@Scheduled注解的。

public class ScheduledAnnotationBeanPostProcessor
  implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
  Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
  SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
  ...
  }

Spring容器加載完bean之后,postProcessAfterInitialization將攔截所有以@Scheduled注解標(biāo)注的方法。

 @Override
 public Object postProcessAfterInitialization(final Object bean, String beanName) {
  Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
  if (!this.nonAnnotatedClasses.contains(targetClass)) {
   //獲取含有@Scheduled注解的方法
   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: " + bean.getClass());
    }
   }
   else {
   
    // 循環(huán)處理包含@Scheduled注解的方法
    annotatedMethods.forEach((method, scheduledMethods) ->
      scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
    if (logger.isDebugEnabled()) {
     logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
       "': " + annotatedMethods);
    }
   }
  }
  return bean;
 }

再往下繼續(xù)看,Spring是如何處理帶有@Schedule注解的方法的。processScheduled獲取scheduled類參數(shù),之后根據(jù)參數(shù)類型、相應(yīng)的延時(shí)時(shí)間、對應(yīng)的時(shí)區(qū)將定時(shí)任務(wù)放入不同的任務(wù)列表中。

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
  try {
   Assert.isTrue(method.getParameterCount() == 0,
     "Only no-arg methods may be annotated with @Scheduled");
   //獲取調(diào)用的方法
   Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
   //處理線程
   Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
   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 delay
   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參數(shù)
   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;
     TimeZone timeZone;
     if (StringUtils.hasText(zone)) {
      timeZone = StringUtils.parseTimeZoneString(zone);
     }
     else {
      timeZone = TimeZone.getDefault();
     }
     //加入到定時(shí)任務(wù)列表中
     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 anymore
   if (initialDelay < 0) {
    initialDelay = 0;
   }
   // Check fixed delay
   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)));
    }
   }
   // 執(zhí)行頻率的類型為long
   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)));
    }
   }
   // Check whether we had any attribute set
   Assert.isTrue(processedSchedule, errorMessage);
   // Finally register the scheduled tasks
   synchronized (this.scheduledTasks) {
    Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
    if (registeredTasks == null) {
     registeredTasks = new LinkedHashSet<>(4);
     this.scheduledTasks.put(bean, registeredTasks);
    }
    registeredTasks.addAll(tasks);
   }
  }
  catch (IllegalArgumentException ex) {
   throw new IllegalStateException(
     "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
  }
 }

滿足條件時(shí)將定時(shí)任務(wù)添加到定時(shí)任務(wù)列表中,在加入任務(wù)列表的同時(shí)對定時(shí)任務(wù)進(jìn)行注冊。ScheduledTaskRegistrar這個(gè)類為Spring容器的定時(shí)任務(wù)注冊中心。以下為ScheduledTaskRegistrar部分源碼,主要說明該類中包含的屬性。Spring容器通過線程處理注冊的定時(shí)任務(wù)。

public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean {
 private TaskScheduler taskScheduler;
 private ScheduledExecutorService localExecutor;
 private List<TriggerTask> triggerTasks;
 private List<CronTask> cronTasks;
 private List<IntervalTask> fixedRateTasks;
 private List<IntervalTask> fixedDelayTasks;
 private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<Task, ScheduledTask>(16);
 private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<ScheduledTask>(16);
 
 ......
}

ScheduledTaskRegistrar類中在處理定時(shí)任務(wù)時(shí)會(huì)調(diào)用scheduleCronTask方法初始化定時(shí)任務(wù)。

public ScheduledTask scheduleCronTask(CronTask task) {
  ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
  boolean newTask = false;
  if (scheduledTask == null) {
   scheduledTask = new ScheduledTask();
   newTask = true;
  }
  if (this.taskScheduler != null) {
   scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
  }
  else {
   addCronTask(task);
   this.unresolvedTasks.put(task, scheduledTask);
  }
  return (newTask ? scheduledTask : null);
 }

在ThreadPoolTaskShcedule這個(gè)類中,進(jìn)行線程池的初始化。在創(chuàng)建線程池時(shí)會(huì)創(chuàng)建 DelayedWorkQueue()阻塞隊(duì)列,定時(shí)任務(wù)會(huì)被提交到線程池,由線程池進(jìn)行相關(guān)的操作,線程池初始化大小為1。當(dāng)有多個(gè)線程需要執(zhí)行時(shí),是需要進(jìn)行任務(wù)等待的,前面的任務(wù)執(zhí)行完了才可以進(jìn)行后面任務(wù)的執(zhí)行。

@Override
 protected ExecutorService initializeExecutor(
   ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
  this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);
  if (this.removeOnCancelPolicy) {
   if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
    ((ScheduledThreadPoolExecutor) this.scheduledExecutor).setRemoveOnCancelPolicy(true);
   }
   else {
    logger.info("Could not apply remove-on-cancel policy - not a Java 7+ ScheduledThreadPoolExecutor");
   }
  }
  return this.scheduledExecutor;
 }

根本原因,jvm啟動(dòng)之后會(huì)記錄系統(tǒng)時(shí)間,然后jvm根據(jù)CPU ticks自己來算時(shí)間,此時(shí)獲取的是定時(shí)任務(wù)的基準(zhǔn)時(shí)間。如果此時(shí)將系統(tǒng)時(shí)間進(jìn)行了修改,當(dāng)Spring將之前獲取的基準(zhǔn)時(shí)間與當(dāng)下獲取的系統(tǒng)時(shí)間進(jìn)行比對時(shí),就會(huì)造成Spring內(nèi)部定時(shí)任務(wù)失效。因?yàn)榇藭r(shí)系統(tǒng)時(shí)間發(fā)生變化了,不會(huì)觸發(fā)定時(shí)任務(wù)。

public ScheduledFuture<?> schedule() {
  synchronized (this.triggerContextMonitor) {
   this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
   if (this.scheduledExecutionTime == null) {
    return null;
   }
   //獲取時(shí)間差
   long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
   this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
   return this;
  }
 }

2.解析流程圖

這里寫圖片描述

3.使用新的方法

為了避免使用@Scheduled注解,在修改服務(wù)器時(shí)間導(dǎo)致定時(shí)任務(wù)不執(zhí)行情況的發(fā)生。在項(xiàng)目中需要使用定時(shí)任務(wù)場景的情況下,使ScheduledThreadPoolExecutor進(jìn)行替代,它任務(wù)的調(diào)度是基于相對時(shí)間的,原因是它在任務(wù)的內(nèi)部 存儲(chǔ)了該任務(wù)距離下次調(diào)度還需要的時(shí)間(使用的是基于 System.nanoTime實(shí)現(xiàn)的相對時(shí)間 ,不會(huì)因?yàn)橄到y(tǒng)時(shí)間改變而改變,如距離下次執(zhí)行還有10秒,不會(huì)因?yàn)閷⑾到y(tǒng)時(shí)間調(diào)前6秒而變成4秒后執(zhí)行)。

schedule定時(shí)任務(wù)修改表達(dá)式無效

真是鬼了。 就那么個(gè)cron表達(dá)式,難道還能錯(cuò)了。

對了無數(shù)遍,cron表達(dá)式?jīng)]問題。 但就是無效。

擴(kuò)展下思路,有沒有用到zookeeper,zookeeper是會(huì)緩存配置信息的。

看了下,果然是緩存了。 清空后,重啟項(xiàng)目有效了。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • idea快捷鍵生成getter和setter,有構(gòu)造參數(shù),無構(gòu)造參數(shù),重寫toString方式

    idea快捷鍵生成getter和setter,有構(gòu)造參數(shù),無構(gòu)造參數(shù),重寫toString方式

    這篇文章主要介紹了java之idea快捷鍵生成getter和setter,有構(gòu)造參數(shù),無構(gòu)造參數(shù),重寫toString方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 詳解SpringBoot實(shí)現(xiàn)JPA的save方法不更新null屬性

    詳解SpringBoot實(shí)現(xiàn)JPA的save方法不更新null屬性

    直接調(diào)用原生Save方法會(huì)導(dǎo)致null屬性覆蓋到數(shù)據(jù)庫,使用起來十分不方便。本文詳細(xì)的介紹了如何解決這個(gè)問題,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-12-12
  • 開發(fā)10年,全記在這本Java進(jìn)階寶典里了

    開發(fā)10年,全記在這本Java進(jìn)階寶典里了

    這篇文章主要給大家分享介紹了這本Java進(jìn)階寶典里,是開發(fā)10年總結(jié)出來的,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧
    2019-04-04
  • Java多線程的用法詳細(xì)介紹

    Java多線程的用法詳細(xì)介紹

    這篇文章主要介紹了Java多線程的用法詳細(xì)介紹的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • 解決因jdk版本引起的TypeNotPresentExceptionProxy異常

    解決因jdk版本引起的TypeNotPresentExceptionProxy異常

    這篇文章介紹了解決因jdk版本引起的TypeNotPresentExceptionProxy異常的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • springboot集成shiro詳細(xì)總結(jié)

    springboot集成shiro詳細(xì)總結(jié)

    這幾天在看 shiro,用 springboot 集成了一下,下面的這個(gè)例子中主要介紹了 shiro 的認(rèn)證和授權(quán),以及鹽值加密的功能.程序可以運(yùn)行起來.這里只做一個(gè)簡單的介紹,后續(xù)會(huì)針對各個(gè)功能做一個(gè)詳細(xì)的介紹,這里不做過多的贅述,需要的朋友可以參考下
    2021-05-05
  • Spring深入刨析聲明式事務(wù)

    Spring深入刨析聲明式事務(wù)

    在spring注解中,使用聲明式事務(wù),需要用到兩個(gè)核心的注解:@Transactional注解和@EnableTransactionManagement注解。將@Transactional注解加在方法上,@EnableTransactionManagement注解加在配置類上
    2022-12-12
  • 詳解堆排序算法原理及Java版的代碼實(shí)現(xiàn)

    詳解堆排序算法原理及Java版的代碼實(shí)現(xiàn)

    如果將堆理解為二叉樹,那么樹中任一非葉結(jié)點(diǎn)的關(guān)鍵字均不大于(或不小于)其左右孩子(若存在)結(jié)點(diǎn)的關(guān)鍵字,堆排序的時(shí)間復(fù)雜度為O(N*logN),這里我們就來詳解堆排序算法原理及Java版的代碼實(shí)現(xiàn)
    2016-06-06
  • Java Lambda表達(dá)式詳解

    Java Lambda表達(dá)式詳解

    這篇文章主要介紹了Java Lambda表達(dá)式詳解,包括了Java Lambda表達(dá)式創(chuàng)建線程,Java Lambda表達(dá)式的語法,Java lambda遍歷List集合,Java lambda過濾String需要的朋友可以參考下
    2023-02-02
  • Spring?Data?JPA實(shí)現(xiàn)查詢結(jié)果返回map或自定義的實(shí)體類

    Spring?Data?JPA實(shí)現(xiàn)查詢結(jié)果返回map或自定義的實(shí)體類

    這篇文章主要介紹了Spring?Data?JPA實(shí)現(xiàn)查詢結(jié)果返回map或自定義的實(shí)體類,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評(píng)論