fixedRate和fixedDelay都是表示任務執行的間隔時間
fixedRate和fixedDelay的區別:
fixedDelay非常好理解,它的間隔時間是根據上次的任務結束的時候開始計時的。比如一個方法上設置了fixedDelay=5*1000,那么當該方法某一次執行結束后,開始計算時間,當時間達到5秒,就開始再次執行該方法。
fixedRate理解起來比較麻煩,它的間隔時間是根據上次任務開始的時候計時的。比如當方法上設置了fiexdRate=5*1000,該執行該方法所花的時間是2秒,那么3秒后就會再次執行該方法。
但是這里有個坑,當任務執行時長超過設置的間隔時長,那會是什么結果呢。打個比方,比如一個任務本來只需要花2秒就能執行完成,我所設置的fixedRate=5*1000,但是因為網絡問題導致這個任務花了7秒才執行完成。當任務開始時Spring就會給這個任務計時,5秒鍾時候Spring就會再次調用這個任務,可是發現原來的任務還在執行,這個時候第二個任務就阻塞了(這里只考慮單線程的情況下,多線程后面再講),甚至如果第一個任務花費的時間過長,還可能會使第三第四個任務被阻塞。被阻塞的任務就像排隊的人一樣,一旦前一個任務沒了,它就立馬執行。
下面用代碼來具體驗證一下。
@SpringBootApplication @EnableScheduling public class ScheduledemoApplication { private AtomicInteger number = new AtomicInteger(); public static void main(String[] args) { SpringApplication.run(ScheduledemoApplication.class, args); } @Scheduled(fixedRate = 5000 ) public void job(){ LocalTime start = LocalTime.now(); //前面和末尾幾個字符串是用來改變打印的顏色的 System.out.println("\033[31;4m" + Thread.currentThread() + " start " + number.incrementAndGet() + " @ " + start + "\033[0m"); try { Thread.sleep(ThreadLocalRandom.current().nextInt(15)*1000); } catch (InterruptedException e) { e.printStackTrace(); } LocalTime end = LocalTime.now(); System.out.println(Thread.currentThread() + " end " + number.get() + " @ "+ end + ", seconds cost "+ (ChronoUnit.SECONDS.between(start, end))); } }
執行結果如下:
可以看到任務第一次執行完以后還有間隔4秒才執行第二次,可是到了第二次結束的時候,就沒有間隔了,直接就執行第三次了。甚至在執行第四次的時候,明明第四次任務只花費了1秒,可還是馬上就執行第五次了。因為前面兩次任務花費的時間太久了,有好幾個被調度的任務都被阻塞了。所以當第四次一結束,馬上第五次就執行了。
@Scheduled(fixedRate)如何避免任務被阻塞
答案是加上注解@EnableAsync(類上)和@Async(方法上),加了注解以后,就開啟了多線程模式,當到了下一次任務的執行時機時,如果上一次任務還沒執行完,就會自動創建一個新的線程來執行它。異步執行也可以理解為保證了任務以固定速度執行。
開啟多線程后執行結果如下:
可以看到,開啟多線程后,每次任務開始的間隔都是5秒鍾。這是符合我們預期的,但是最后還有點缺陷,這種情況下的線程是隨着任務一執行完就銷毀的,等下次有需要了程序再創建一個。每次都要重新創建明顯是太影響性能了,所以需要在代碼里給他一個線程池。
創建一個線程池
@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(5); return taskScheduler; }
可以看到現在程序就不會再自己創建線程了,每次都會從線程池里面拿。需注意的是,如果線程池里的所有線程都被拿去執行調度任務了,且又到了時間要執行一次任務,那么這個任務又會被阻塞。所以實際開發中如果想要保證任務以速度被執行,線程池的最大線程數量可要想好。
話說真的要深入理解的話其實還得看源碼,這些測試只是通過表象來猜測。
over