Spring @Scheduled @Async聯合實現調度任務(2017.11.28更新)


定時任務之前一直用的是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);
 *     }
 * }

標紅處就是使用線程池的配置,之后再執行不僅以多線程來啟動定時任務,而且也不會出現定時任務重復並發執行的問題.至此此問題圓滿解決.再感嘆下啥都不如看源碼.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM