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

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

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

Spring Schedule 實現定時任務

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

1. 創建一個 scheduled task

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

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

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


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++;
}
}
/<integer>

運行程序輸出如下:

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

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

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

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

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


分享到:


相關文章: