激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - 我勸你謹(jǐn)慎使用Spring中的@Scheduled注解

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

2022-02-21 14:31慕楓技術(shù)筆記 Java教程

這篇文章主要介紹了Spring中的@Scheduled注解使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

引言

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

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

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

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

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

1.@Scheduled失效原因

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@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注解的。

?
1
2
3
4
5
6
public class ScheduledAnnotationBeanPostProcessor
  implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
  Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
  SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
  ...
  }

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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)的延時時間、對應(yīng)的時區(qū)將定時任務(wù)放入不同的任務(wù)列表中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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();
     }
     //加入到定時任務(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());
  }
 }

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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類中在處理定時任務(wù)時會調(diào)用scheduleCronTask方法初始化定時任務(wù)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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這個類中,進(jìn)行線程池的初始化。在創(chuàng)建線程池時會創(chuàng)建 DelayedWorkQueue()阻塞隊(duì)列,定時任務(wù)會被提交到線程池,由線程池進(jìn)行相關(guān)的操作,線程池初始化大小為1。當(dāng)有多個線程需要執(zhí)行時,是需要進(jìn)行任務(wù)等待的,前面的任務(wù)執(zhí)行完了才可以進(jìn)行后面任務(wù)的執(zhí)行。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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啟動之后會記錄系統(tǒng)時間,然后jvm根據(jù)CPU ticks自己來算時間,此時獲取的是定時任務(wù)的基準(zhǔn)時間。如果此時將系統(tǒng)時間進(jìn)行了修改,當(dāng)Spring將之前獲取的基準(zhǔn)時間與當(dāng)下獲取的系統(tǒng)時間進(jìn)行比對時,就會造成Spring內(nèi)部定時任務(wù)失效。因?yàn)榇藭r系統(tǒng)時間發(fā)生變化了,不會觸發(fā)定時任務(wù)。

?
1
2
3
4
5
6
7
8
9
10
11
12
public ScheduledFuture<?> schedule() {
  synchronized (this.triggerContextMonitor) {
   this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
   if (this.scheduledExecutionTime == null) {
    return null;
   }
   //獲取時間差
   long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
   this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
   return this;
  }
 }

2.解析流程圖

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

3.使用新的方法

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

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

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

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

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

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

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://blog.csdn.net/Diamond_Tao/article/details/80628440

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 99精品欧美一区二区 | 一区二区三区在线观看视频 | 色播久久 | 91色综合综合热五月激情 | 逼特逼视频在线观看 | 中国美女一级黄色大片 | 在线小视频国产 | 国内免费视频成人精品 | 精品亚洲视频在线 | 精品成人av一区二区在线播放 | 国产精品亚洲一区二区三区在线观看 | 最近高清无吗免费看 | 午夜色视频在线观看 | 日本成人午夜视频 | 日韩黄色在线播放 | 国产91亚洲精品一区二区三区 | 日韩一级片免费 | 亚洲精品无码不卡在线播放he | 欧美三级短视频 | 免费h片网站 | 午夜爽爽爽男女免费观看hd | 黄污在线观看 | 一级大片视频 | 国产精品久久久久久久久久三级 | 精品亚洲二区 | av在线免费观看中文字幕 | 九九色精品 | 免费视频aaa | 久久久综合久久久 | 黄色7777| 娇喘视频在线观看 | 精品午夜影院 | 天堂亚洲一区 | 中国久久久| 91成人在线免费视频 | 久久噜噜噜| 久久久精品视频免费看 | 欧美精品一区自拍a毛片在线视频 | 国产草草视频 | av电影免费观看 | 亚洲男人一区 |