理解Spring定時任務@Scheduled的兩個屬性fixedRate和fixedDelay


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


免責聲明!

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



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