定時任務之前一直用的是quartz之類,但是注意到Spring中其實也提供了一種簡單的調度注釋@Scheduled,也就想嘗一下鮮..
代碼示意如下:
@Component @EnableScheduling public class AsyncTaskHandlerTask { @Scheduled(fixedDelay = 1000) public void task1() { //輸出日志 } @Scheduled(fixedDelay = 1000) public void task2() { //輸出日志 } }
執行了一下,完全ok,日志打印正常,2個任務也都正常定時執行了.那好,添加些業務邏輯進去:
@Component @EnableScheduling public class AsyncTaskHandlerTask { @Scheduled(fixedDelay = 1000) public void task1() { while(true){ .... } } @Scheduled(fixedDelay = 1000) public void task2() { while(true){ .... } } }
再啟動,咦,奇怪了,怎么定時任務沒有執行呢?倘使我之前沒有輸出日志試驗,我可能就認為注解的用法錯了呢...重新添加日志,下斷點重跟了一下啟動過程發現:
程序進入到while死循環后就卡死了,沒有再繼續啟動另一個定時任務了.通過現象可知@Scheduled啟動過程是一個單線程同步啟動過程,故一旦中途被阻塞,會導致整個啟動過程阻塞,
其余的定時任務都不會啟動.這明顯很奇怪,網上的教程大多數是xml配置形式,Spring的官網我這頭打開又奇慢無比..但是從xml的配置形式可知需要配置一個線程池來啟動定時任
務.但是Javaconfig形式的則沒有說明.但是我查詢到了另一個注解@Async,這個異步注解我是使用過的,可以指定線程池,打到方法上后便會以指定的線程池來執行方法.然后解決方案來了:
@Component @EnableScheduling public class AsyncTaskHandlerTask { @Scheduled(fixedDelay = 1000) @Async public void task1() { while(true){ .... } } @Scheduled(fixedDelay = 1000) @Async public void task2() { while(true){ .... } } }
再次啟動,不會再被阻塞.
2017-11-28更新:
本來以為找到了正解,結果證明是誤入了歧途..以上是完全錯誤的使用,雖然看起來貌似是正確的..正應了一句話啊,啥都不如看源碼啊...
太懶了,本來這個應該再單獨開個文章再說的,但是太懶了...
進入正題,按照如上實現部署后突然發現一個詭異的問題,定時任務發生了異常的"阻塞"現象,某個任務突然看起來不再執行了,直接卡死了.第一印象是死鎖了,直接jstack生成了一份堆棧信息,仔細分析后發現一個詭異問題,定時任務並沒有像Scheduled定義的那樣一次結束后再執行下一次,而是並發執行多次,直接將Async定義的線程池跑滿.那么問題也就好解釋了,Async注解后,直接異步在新線程中執行任務,由於異步執行Scheduled認為上一次已經執行完馬上開始執行下一次,導致不停執行定時任務直接跑滿Async使用的線程池.那么這實際上是一種錯誤的使用方法..怎么辦?
只好點了@EnableScheduling注解看一下源碼中怎么說的吧.
* <p>When more control is desired, a {@code @Configuration} class may implement * {@link SchedulingConfigurer}. This allows access to the underlying * {@link ScheduledTaskRegistrar} instance. For example, the following example * demonstrates how to customize the {@link Executor} used to execute scheduled * tasks:
實際上已經說的很明白了,更多的控制,只需要繼承 SchedulingConfigurer 這個類,之前沒有找到對應xml中配置線程池的方法也正是如此.
* public class AppConfig implements SchedulingConfigurer { * * @Override * public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { * taskRegistrar.setScheduler(taskExecutor()); * } * * @Bean(destroyMethod="shutdown") * public Executor taskExecutor() { * return Executors.newScheduledThreadPool(100); * } * }
標紅處就是使用線程池的配置,之后再執行不僅以多線程來啟動定時任務,而且也不會出現定時任務重復並發執行的問題.至此此問題圓滿解決.再感嘆下啥都不如看源碼.