一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

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

服務器之家 - 編程語言 - Java教程 - 我勸你謹慎使用Spring中的@Scheduled注解

我勸你謹慎使用Spring中的@Scheduled注解

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

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

引言

在一些業務場景中需要執行定時操作來完成一些周期性的任務,比如每隔一周刪除一周前的某些歷史數據以及定時進行某項檢測任務等等。

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

但是在修改服務器時間時會導致定時任務不執行情況的發生,解決的辦法是當修改服務器時間后,將服務進行重啟就可以避免此現象的發生。

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

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

1.@Scheduled失效原因

(1)首先我們一起看一下@Scheduled注解的源碼,主要說明了注解可使用的參數形式,在注解中使用了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注解標注的方法。

?
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 {
  
   // 循環處理包含@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;
}

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

?
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");
   //獲取調用的方法
   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參數
   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();
     }
     //加入到定時任務列表中
     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)));
    }
   }
   // 執行頻率的類型為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());
  }
 }

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

?
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類中在處理定時任務時會調用scheduleCronTask方法初始化定時任務。

?
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這個類中,進行線程池的初始化。在創建線程池時會創建 DelayedWorkQueue()阻塞隊列,定時任務會被提交到線程池,由線程池進行相關的操作,線程池初始化大小為1。當有多個線程需要執行時,是需要進行任務等待的,前面的任務執行完了才可以進行后面任務的執行。

?
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啟動之后會記錄系統時間,然后jvm根據CPU ticks自己來算時間,此時獲取的是定時任務的基準時間。如果此時將系統時間進行了修改,當Spring將之前獲取的基準時間與當下獲取的系統時間進行比對時,就會造成Spring內部定時任務失效。因為此時系統時間發生變化了,不會觸發定時任務。

?
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.解析流程圖

我勸你謹慎使用Spring中的@Scheduled注解

3.使用新的方法

為了避免使用@Scheduled注解,在修改服務器時間導致定時任務不執行情況的發生。在項目中需要使用定時任務場景的情況下,使ScheduledThreadPoolExecutor進行替代,它任務的調度是基于相對時間的,原因是它在任務的內部 存儲了該任務距離下次調度還需要的時間(使用的是基于 System.nanoTime實現的相對時間 ,不會因為系統時間改變而改變,如距離下次執行還有10秒,不會因為將系統時間調前6秒而變成4秒后執行)。

schedule定時任務修改表達式無效

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

對了無數遍,cron表達式沒問題。 但就是無效。

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

看了下,果然是緩存了。 清空后,重啟項目有效了。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 好男人好资源在线观看免费 | 成人在线一区二区三区 | 深夜福利免费在线观看 | pregnantsexxx临盆孕妇 | 国产大神91一区二区三区 | 久久成人精品免费播放 | 美女污视频在线观看 | 国产精品香蕉一区二区三区 | 亚洲欧美日韩精品高清 | 日韩一级精品视频在线观看 | 日韩香蕉网| 精品蜜臀AV在线天堂 | 色综合色狠狠天天综合色hd | 男人摸女人下面 | 亚洲高清在线精品一区 | 国产精品最新资源网 | 亚洲天堂网站 | 欧美一区二区三区综合色视频 | 久久久91精品国产一区二区 | 亚色九九九全国免费视频 | 亚洲aⅴ天堂 | 男人天堂色 | 国产成人愉拍免费视频 | 日日操美女 | 日本高清动作片www欧美 | 俄罗斯精品bbw | 手机在线伦理片 | 视频大全在线观看网址 | 国产成人h视频在线播放网站 | 欧美日韩久久中文字幕 | 国自产精品手机在线视频 | 特级一级全黄毛片免费 | 国产v在线在线观看羞羞答答 | 亚洲 制服 欧美 中文字幕 | 我和岳的性事小说 | 亚洲日本va午夜中文字幕 | 久久这里只有精品视频9 | 四虎精品在线观看 | 国产精品亚洲精品日韩已方 | 无码中文字幕av免费放 | 国产成人免费在线观看 |