@Scheduled 定時任務

weiewiyi發表於2022-02-15

在開發中,有時候會遇到定時推送的需求,例如定時傳送推送、定時傳送郵件等等。SpringBoot為我們內建了定時任務,我們只需要一個註解就可以開啟定時為我們所用了。

開啟定時任務

在入口類LogApplication中新增 @EnableScheduling 註解,新增註解後SpringBoot就已經認定了我們要使用定時任務來完成一些業務邏輯了,內部會對應原始配置定時任務新增對應的配置檔案。

@SpringBootApplication
@EnableScheduling    +    // 啟用定時任務
public class LogApplication {

image.png

@Scheduled

配置定時任務非常簡單,只需要在需要定時執行的方法上新增 @Scheduled 註解即可注意該類上需要打上元件型註解,例如 @Componet,這樣該類才會被注入到 Spring 容器中進行管理,用來標明這是一個被Spring管理的Bean,@Scheduled 才會生效。
當然一般會使用@Coponent的衍生註解:@Repository, @Service, @Controller來替代,這裡不展開講了。

@Service  +
public class DayLogTaskImpl implements DayLogTask {
    @Scheduled(cron = "* * * * * ?")  +
    public void test1() {

image.png
現在來測試一下程式碼:

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

執行結果:

2022-02-14 13:46:50.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 執行: 1644828410000
2022-02-14 13:46:51.003  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 執行: 1644828411003
2022-02-14 13:46:52.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 執行: 1644828412000
2022-02-14 13:46:53.000  INFO 2337 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : scheduler1 執行: 1644828413000

可以看到列印出的時間相隔都大約是1000毫秒左右,符合定義的每秒執行一次。

@Scheduled 註解引數說明

cron:該引數接收一個cron表示式,cron表示式是一個字串,如image.png表示每5秒執行一次。
zone: 時區。預設為伺服器所在時區,接收型別為 java.util.TimeZone。
fixedDelay:上一次執行完畢時間點之後多長時間再執行。如:

@Scheduled(fixedDelay = 10000) //上一次執行完畢時間點之後10秒再執行

fixedRate: 表示在上次執行開始之後多久後再次執行。用法同上。
initialDelay: 表示第一次任務執行延遲多久。用法同上。

@Scheduled的執行緒問題

@Scheduled 預設是單執行緒執行的,多個 @Scheduled 任務都用的同一個執行緒。如果某個任務是個耗時的操作,那麼其它定時任務都會等待該任務的執行完成,從而造成堵塞。
比如下面我們給出一個例子:兩個無關的定時任務:列印日誌print生成表格generate,都是需要每秒觸發一次。如果generate執行一次需要耗時 3 秒,則printgenerate 都要等這一次的generate執行後才能再執行,這就達不到任務print、generate 每秒執行一次的效果了。

我們用程式碼來測試一下

我們用Thread.sleep()模擬執行方法時的耗時。兩者都是每秒執行一次,其中test1睡眠3秒模擬耗時。

 @Scheduled(cron = "* * * * * ?")
    public void test1() {
        /**
         * 每秒執行一次
         */
        logger.info("test1執行並睡眠3秒: " + System.currentTimeMillis());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("test1執行結束: " + System.currentTimeMillis());
    }

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

執行結果:

2022-02-14 14:09:41.004  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644829781004
2022-02-14 14:09:44.007  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644829784007
2022-02-14 14:09:44.010  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644829784010
2022-02-14 14:09:45.005  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644829785005
2022-02-14 14:09:48.007  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644829788007
2022-02-14 14:09:48.008  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644829788008
2022-02-14 14:09:49.009  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644829789009
2022-02-14 14:09:49.011  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644829789011
2022-02-14 14:09:52.012  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644829792012
2022-02-14 14:09:52.013  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644829792013
2022-02-14 14:09:53.005  INFO 2484 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644829793005

可以看到執行的執行緒都是[ scheduling-1],並且test2執行間隔並不固定是設定好的1s,而是 有時是1s,有時是3s,4s。

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

使用 @Async 非同步執行方法

與新增定時任務相同,在入口類中新增 @EnableAsync,開啟非同步執行。在定時任務上新增 @Async 註解,標識該方法非同步執行。方法程式碼與上面的相同。

    @Async  +
    @Scheduled(cron = "* * * * * ?")
    public void test1() {

這時候我們再執行程式碼看看效果:

2022-02-14 14:45:04.001  INFO 2608 --- [         task-4] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644831904001
2022-02-14 14:45:04.001  INFO 2608 --- [         task-1] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644831904001
2022-02-14 14:45:05.002  INFO 2608 --- [         task-2] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644831905002
2022-02-14 14:45:05.002  INFO 2608 --- [         task-5] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644831905002
2022-02-14 14:45:05.003  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644831905003
2022-02-14 14:45:06.001  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644831906001
2022-02-14 14:45:06.001  INFO 2608 --- [         task-6] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644831906001
2022-02-14 14:45:06.008  INFO 2608 --- [         task-3] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644831906008
2022-02-14 14:45:07.001  INFO 2608 --- [   scheduling-1] club.yunzhi.log.task.DayLogTask          : test2 執行: 1644831907001
2022-02-14 14:45:07.001  INFO 2608 --- [         task-4] club.yunzhi.log.task.DayLogTask          : test1執行結束: 1644831907001
2022-02-14 14:45:07.002  INFO 2608 --- [         task-7] club.yunzhi.log.task.DayLogTask          : test1執行並睡眠3秒: 1644831907002

雖然方法執行需要耗時 3 秒,但是每個任務還是按照每秒鐘觸發一次準時執行了。通過前面的 執行緒號 可以看出,任務的執行使用了不同的執行緒。test2用的是scheduling-1的執行緒號,每秒一次。而test1非同步則用了多個執行緒號。

@Async 預設執行緒池個數為 8 個。
預設的 @Async 可以應付一般的場景,但如果是併發量比較高的情況下,就會存在一定風險。例如開銷過大、記憶體溢位等。為使服務執行穩定,我們可以自定義配置執行緒池,然後讓給需要非同步執行的方法指定用該執行緒池執行。

簡單來說就是配置一個Executor,配置核心執行緒數,最大執行緒數,佇列大小等資訊。用@Async("myExecutor")指定該呼叫該執行緒池。

@Configuration
public class ExecutorConfig {
  @Bean(name = "myExecutor")
  public Executor taskExecutor() {

具體可以參考這篇文章:https://www.interhorse.cn/a/3...

目前的專案中定時任務執行的方法較快,且間隔在分鐘級別,所以沒有用到多執行緒定時任務,記錄一下防止以後需要。

以上為定時任務的相關內容。

相關文章