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