5分鐘搞懂如何在Spring Boot中Schedule Tasks

本文內容已經同步更新到 Github:https://github.com/Snailclimb... ,歡迎 star。

很多時候我們都需要爲系統建立一個定時任務來幫我們做一些事情,SpringBoot 已經幫我們實現好了一個,我們只需要直接使用即可,當然你也可以不用 SpringBoot 自帶的定時任務,整合 Quartz 很多時候也是一個不錯的選擇。

本文不涉及 SpringBoot 整合 Quartz 的內容,只演示瞭如何使用 SpringBoot 自帶的實現定時任務的方式。

Spring Schedule 實現定時任務

我們只需要 SpringBoot 項目最基本的依賴即可,所以這裏就不貼配置文件了。

1. 創建一個 scheduled task

我們使用 @Scheduled 註解就能很方便地創建一個定時任務,下面的代碼中涵蓋了 @Scheduled 的常見用法,包括:固定速率執行、固定延遲執行、初始延遲執行、使用 Cron 表達式執行定時任務。

Cron 表達式: 主要用於定時作業(定時任務)系統定義執行時間或執行頻率的表達式,非常厲害,你可以通過 Cron 表達式進行設置定時任務每天或者每個月什麼時候執行等等操作。

推薦一個在線Cron表達式生成器:http://cron.qqe2.com/

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author shuang.kou
 */
@Component
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    /**
     * fixedRate:固定速率執行。每5秒執行一次。
     */
    @Scheduled(fixedRate = 5000)
    public void reportCurrentTimeWithFixedRate() {
        log.info("Current Thread : {}", Thread.currentThread().getName());
        log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * fixedDelay:固定延遲執行。距離上一次調用成功後2秒才執。
     */
    @Scheduled(fixedDelay = 2000)
    public void reportCurrentTimeWithFixedDelay() {
        try {
            TimeUnit.SECONDS.sleep(3);
            log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * initialDelay:初始延遲。任務的第一次執行將延遲5秒,然後將以5秒的固定間隔執行。
     */
    @Scheduled(initialDelay = 5000, fixedRate = 5000)
    public void reportCurrentTimeWithInitialDelay() {
        log.info("Fixed Rate Task with Initial Delay : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * cron:使用Cron表達式。 每分鐘的1,2秒運行
     */
    @Scheduled(cron = "1-2 * * * * ? ")
    public void reportCurrentTimeWithCronExpression() {
        log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
    }
}

關於 fixedRate 這裏其實有個坑,假如我們有這樣一種情況:我們某個方法的定時器設定的固定速率是每5秒執行一次。這個方法現在要執行下面四個任務,四個任務的耗時是:6 s、6s、 2s、 3s,請問這些任務默認情況下(單線程)將如何被執行?

我們寫一段簡單的程序驗證:

    private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    private List<Integer> index = Arrays.asList(6, 6, 2, 3);
    int i = 0;
    @Scheduled(fixedRate = 5000)
    public void reportCurrentTimeWithFixedRate() {
        if (i == 0) {
            log.info("Start time is {}", dateFormat.format(new Date()));
        }
        if (i < 5) {
            try {
                TimeUnit.SECONDS.sleep(index.get(i));
                log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
    }

運行程序輸出如下:

Start time is 20:58:33
Fixed Rate Task : The time is now 20:58:39
Fixed Rate Task : The time is now 20:58:45
Fixed Rate Task : The time is now 20:58:47
Fixed Rate Task : The time is now 20:58:51

看下面的運行任務示意圖應該很好理解了。

如果我們將這個方法改爲並行運行,運行結果就截然不同了。

 2. 啓動類上加上@EnableScheduling註解

在 SpringBoot 中我們只需要在啓動類上加上@EnableScheduling便可以啓動定時任務了。

@SpringBootApplication
@EnableScheduling
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3. 自定義線程池執行 scheduled task

默認情況下,@Scheduled任務都在Spring創建的大小爲1的默認線程池中執行,你可以通過在加了@Scheduled註解的方法里加上下面這段代碼來驗證。

logger.info("Current Thread : {}", Thread.currentThread().getName());

你會發現加上上面這段代碼的定時任務,每次運行都會輸出:

Current Thread : scheduling-1

如果我們需要自定義線程池執行話只需要新加一個實現SchedulingConfigurer接口的 configureTasks 的類即可,這個類需要加上 @Configuration 註解。

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    private final int POOL_SIZE = 10;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
        threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }
}

通過上面的驗證的方式輸出當前線程的名字會改變。

4. @EnableAsync 和 @Async 使定時任務並行執行

如果你想要你的代碼並行執行的話,還可以通過@EnableAsync 和 @Async 這兩個註解實現

@Component
@EnableAsync
public class AsyncScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    /**
     * fixedDelay:固定延遲執行。距離上一次調用成功後2秒才執。
     */
    //@Async
    @Scheduled(fixedDelay = 2000)
    public void reportCurrentTimeWithFixedDelay() {
        try {
            TimeUnit.SECONDS.sleep(3);
            log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行程序輸出如下,reportCurrentTimeWithFixedDelay() 方法會每5秒執行一次,因爲我們說過了@Scheduled任務都在Spring創建的大小爲1的默認線程池中執行。

Current Thread : scheduling-1
Fixed Delay Task : The time is now 14:24:23
Current Thread : scheduling-1
Fixed Delay Task : The time is now 14:24:28
Current Thread : scheduling-1
Fixed Delay Task : The time is now 14:24:33

reportCurrentTimeWithFixedDelay() 方法上加上 @Async 註解後輸出如下,reportCurrentTimeWithFixedDelay() 方法會每 2 秒執行一次。

Current Thread : task-1
Fixed Delay Task : The time is now 14:27:32
Current Thread : task-2
Fixed Delay Task : The time is now 14:27:34
Current Thread : task-3
Fixed Delay Task : The time is now 14:27:36

公衆號

如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公衆號。

《Java面試突擊》: 由本文檔衍生的專爲面試而生的《Java面試突擊》V2.0 PDF 版本公衆號後臺回覆 "Java面試突擊" 即可免費領取!

Java工程師必備學習資源: 一些Java工程師常用學習資源公衆號後臺回覆關鍵字 “1” 即可免費無套路獲取。

我的公衆號

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