SpringBoot 開發實踐(5):定時任務 @Scheduled

前言

在開發中,我們會有定時執行某些任務的需求,例如定時清理過期文件、定時發送郵件等等。SpringBoot 爲我們提供了便捷的方式來配置定時任務,只需要打上幾個註解即可。那麼下面讓我們來看看 SpringBoot 中如何開發定時任務。

開啓定時任務

想要使用定時任務,需先打開定時任務開關。
在入口類中添加 @EnableScheduling 註解

@SpringBootApplication
@EnableScheduling
public class SchedulerTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchedulerTaskApplication.class, args);
    }
}

使用 @Scheduled 配置定時任務

配置定時任務非常簡單,只需要在需要定時執行的方法上添加 @Scheduled 註解即可。注意,該類上需要打上組件型註解,例如 @Componet,這樣該類纔會被注入到 Spring 容器中進行管理,@Scheduled 纔會生效。例如:

@Component
public class SchedulerTask1 {
    private static final Logger LOG = LoggerFactory.getLogger(SchedulerTask1.class);

/**
 * 每秒執行一次
 */    
@Scheduled(cron = "* * * * * ?")
    public void scheduler1() {
        LOG.info("scheduler1 測試: " + System.currentTimeMillis());
    }
}

運行程序,結果如下:

2020-06-19 00:28:06.002  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497686002
2020-06-19 00:28:07.003  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497687003
2020-06-19 00:28:08.002  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497688002
2020-06-19 00:28:09.002  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497689002
2020-06-19 00:28:10.001  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497690001
2020-06-19 00:28:11.001  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497691001
2020-06-19 00:28:12.005  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497692005
2020-06-19 00:28:13.001  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 測試: 1592497693001

可以看到,日誌每秒鐘打印一次,這說明該方法每秒鐘被執行一次。

@Scheduled 註解參數說明

@Scheduled支持多種定時規則進行配置。

  • cron: cron 表達式是 Linux 中定時任務的配置規則。具體使用方式可以參考 cron。文末也附上了一些配置示例,方便大家參考。
  • zone: 時區。默認爲服務器所在時區,接收類型爲 java.util.TimeZone
  • fixedDelay / fixedDelayString: 表示在上次執行之後多久後再次執行。它倆的區別爲前者類型爲 long 類型,後者爲 String,單位均爲 ms。
  • fixedRate / fixedRateString: 表示在上次執行開始之後多久後再次執行。同樣,它倆的區別也僅爲參數類型的區別。
  • initialDelay / initialDelayString: 表示第一次任務執行延遲多久,區別同上。

@Scheduled 的多線程使用

@Scheduled 默認是單線程執行的,多個 @Scheduled 任務都用的同一個線程。如果某個任務是個耗時的操作,那麼其它定時任務都會因爲這一個耗時的任務而堵塞。

例如,兩個定時任務 A、B 都是每秒觸發一次。如果任務 A 執行一次需要耗時 5 秒,則任務 A、B 都要等任務 A 執行後才能再執行,這就達不到任務 A、B 每秒執行一次的效果了。

那麼,如何能讓每個定時任務按照配置好的定時規則準時執行呢?這就需要我們將定時任務配置成多線程的方式。

在入口類中添加 @EnableAsync,開啓異步執行。在定時任務上添加 @Async 註解,標識該方法異步執行。方法中我們通過 sleep 來模擬耗時操作。

@Component
public class SchedulerTask2 {
    private static final Logger LOG = LoggerFactory.getLogger(SchedulerTask2.class);

    /**
     * 每秒執行一次
     */
    @Async
    @Scheduled(cron = "* * * * * ?")
    public void scheduler1() {
        LOG.info("SchedulerTask2 scheduler1 執行: " + System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOG.info("SchedulerTask2 scheduler1 執行完了: " + System.currentTimeMillis());
    }
}

運行程序我們可以看到:

2020-06-19 01:13:04.011  INFO 13495 --- [         task-1] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500384011
2020-06-19 01:13:05.002  INFO 13495 --- [         task-2] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500385002
2020-06-19 01:13:06.002  INFO 13495 --- [         task-3] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500386001
2020-06-19 01:13:07.005  INFO 13495 --- [         task-4] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500387004
2020-06-19 01:13:08.003  INFO 13495 --- [         task-5] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500388003
2020-06-19 01:13:09.003  INFO 13495 --- [         task-6] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500389003
2020-06-19 01:13:09.013  INFO 13495 --- [         task-1] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行完了: 1592500389013
2020-06-19 01:13:10.004  INFO 13495 --- [         task-2] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行完了: 1592500390004
2020-06-19 01:13:10.004  INFO 13495 --- [         task-7] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500390004
2020-06-19 01:13:11.002  INFO 13495 --- [         task-3] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行完了: 1592500391002
2020-06-19 01:13:11.003  INFO 13495 --- [         task-8] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 執行: 1592500391003

雖然方法執行需要耗時 5 秒,但是每個任務還是按照每秒鐘觸發一次準時執行了。通過前面的 task-n 可以看出,每個任務的執行都使用了不同的線程。

@Async 默認線程池個數爲 8 個,我們也可以通過配置來滿足不同場景的需要。有關 @Async 多線程的使用,我會在後續的章節爲大家介紹。

以上就是 SpringBoot 定時任務的使用方法。

附1:Cron 表達式示例

0 0 2 1 * ? * 表示在每月的1日的凌晨2點調度任務
0 15 10 ? * MON-FRI 表示週一到週五每天上午10:15執行作業
0 15 10 ? * 6L 2002-2006 表示2002-2006年的每個月的最後一個星期五上午10:15執行作
0 0 10,14,16 * * ? 每天上午10點,下午2點,4點
0 0/30 9-17 * * ? 朝九晚五工作時間內每半小時
0 0 12 ? * WED 表示每個星期三中午12點
0 0 12 * * ? 每天中午12點觸發
0 15 10 ? * * 每天上午10:15觸發
0 15 10 * * ? 每天上午10:15觸發
0 15 10 * * ? * 每天上午10:15觸發
0 15 10 * * ? 2005 2005年的每天上午10:15觸發
0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鐘觸發
0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鐘觸發
0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鐘觸發
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發
0 15 10 ? * MON-FRI 週一至週五的上午10:15觸發
0 15 10 15 * ? 每月15日上午10:15觸發
0 15 10 L * ? 每月最後一日的上午10:15觸發
0 15 10 ? * 6L 每月的最後一個星期五上午10:15觸發
0 15 10 ? * 6L 2002-2005 002年至2005年的每月的最後一個星期五上午10:15觸發
0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發

本章代碼:GitHub


我是因特馬,一個愛分享的斜槓程序員~

歡迎關注我的公衆號:一隻因特馬

原文作者: 一隻因特馬
原文鏈接: https://www.interhorse.cn/a/1174570529/
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-ND 許可協議。轉載請註明出處!

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