SpringBoot踩坑日記-定時任務不定時了?

問題描述br/>springboot定時任務用起來大家應該都會用,加兩註解,加點配置就可以運行。但是如果僅僅處在應用層面的話,有很多內在的問題開發中可能難以察覺。話不多說,我先用一種極度誇張的手法,描述一下遇到的一個問題。
@Component
public class ScheduleTest {
@Scheduled(initialDelay = 1000,fixedRate = 2*1000)
public void test_a(){
System.out.println("123");
}

@Scheduled(initialDelay = 2*1000,fixedRate = 2*1000)
public void test_b(){
    while (true){
        try {
            Thread.sleep(2*1000);
            System.out.println("456");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

}
複製代碼上面代碼是一個項目中的兩個定時任務,test_a是正常的方法,test_b是發生異常的方法,爲了凸顯異常,我搞了個死循環。
在這種情況下,使用默認的定時任務配置運行,會發生什麼現象呢?試試看就知道了,定時任務一直在方法b中循環着,方法a永遠執行不到!!!

問題原因
查看源代碼後發現SpringBoot源碼解析-Scheduled定時器的原理,springboot中,默認的定時任務線程池是隻有一個線程的,所以如果在一堆定時任務中,有一個發生了延時或者死循環之類的異常,很大可能會影響到其他的定時任務。
解決方案
既然問題出在線程池數量上,那麼爲了讓各個任務之間不會互相干擾,那就配置相應的線程池就好了。
方案一 異步執行
@Scheduled(initialDelay = 1000,fixedRate = 2*1000)br/>@Async
public void test_a(){
System.out.println("123");
}

@Scheduled(initialDelay = 2*1000,fixedRate = 2*1000)
@Async
public void test_b(){
    while (true){
        try {
            Thread.sleep(2*1000);
            System.out.println("456");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

複製代碼既然在單線程中因爲一個任務卡住而影響到其他任務,那麼把這個任務異步執行,問題就解決啦。
方案二 自定義定時任務線程池數量br/>@Configuration
public class GlobalConfiguration {

@Bean
public TaskScheduler schedule(){
    return new ConcurrentTaskScheduler(new ScheduledThreadPoolExecutor(2));
}

}
複製代碼既然單線程會互相干擾,那麼分配足夠的線程,讓他們各自分開運行,也是可以解決的。
兩種方案對比
兩種方案都可以解決各個任務之間互相干擾的問題,但是需要根據實際情況選擇合適的。我們就以上面出現死循環的代碼來分析。
如果在定時任務中真的發生了死循環,那麼使用異步執行則會帶來災難性的後果。因爲在定時任務這個線程中,每次任務執行完畢後,他會計算下次時間,再次添加一個任務進入異步線程池。而添加進異步線程池的任務因爲死循環而一直佔用着線程資源。隨着時間的增加異步線程池的所有線程資源都會被死循環的任務佔據,導致其他服務全部阻塞。
而使用自定義定時任務線程池則會好一點,因爲只有當任務執行完成後,纔會計算時間,在執行下次任務。雖然因爲死循環任務一直在執行,但是也頂多佔據一個線程的資源,不至於更大範圍的影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章