問題描述
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)
@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();
}
}
}
複製程式碼
既然在單執行緒中因為一個任務卡住而影響到其他任務,那麼把這個任務非同步執行,問題就解決啦。
方案二 自定義定時任務執行緒池數量
@Configuration
public class GlobalConfiguration {
@Bean
public TaskScheduler schedule(){
return new ConcurrentTaskScheduler(new ScheduledThreadPoolExecutor(2));
}
}
複製程式碼
既然單執行緒會互相干擾,那麼分配足夠的執行緒,讓他們各自分開執行,也是可以解決的。
兩種方案對比
兩種方案都可以解決各個任務之間互相干擾的問題,但是需要根據實際情況選擇合適的。我們就以上面出現死迴圈的程式碼來分析。
如果在定時任務中真的發生了死迴圈,那麼使用非同步執行則會帶來災難性的後果。因為在定時任務這個執行緒中,每次任務執行完畢後,他會計算下次時間,再次新增一個任務進入非同步執行緒池。而新增進非同步執行緒池的任務因為死迴圈而一直佔用著執行緒資源。隨著時間的增加非同步執行緒池的所有執行緒資源都會被死迴圈的任務佔據,導致其他服務全部阻塞。
而使用自定義定時任務執行緒池則會好一點,因為只有當任務執行完成後,才會計算時間,在執行下次任務。雖然因為死迴圈任務一直在執行,但是也頂多佔據一個執行緒的資源,不至於更大範圍的影響。