1. 程式人生 > >SpringBoot踩坑日記-定時任務不定時了?

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

exceptio 對比 源代碼 trac 默認 範圍 sys 有一個 既然

問題描述br/>springboot定時任務用起來大家應該都會用,加兩註解,加點配置就可以運行。但是如果僅僅處在應用層面的話,有很多內在的問題開發中可能難以察覺。話不多說,我先用一種極度誇張的手法,描述一下遇到的一個問題。
@Component
@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
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

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

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

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