3分鐘帶你搞定Spring Boot中Schedule
本文轉載自微信公眾號「Java極客技術」,作者鴨血粉絲。轉載本文請聯系Java極客技術公眾號。
一、摘要
閱讀完本文大概需要3分鐘,本文主要分享內容如下:
- SpringBoot Schedule 實踐介紹
二、介紹
在實際的業務開發過程中,我們經常會需要定時任務來幫助我們完成一些工作,例如每天早上 6 點生成銷售報表、每晚 23 點清理臟數據等等。
如果你當前使用的是 SpringBoot 來開發項目,那么完成這些任務會非常容易!
SpringBoot 默認已經幫我們完成了相關定時任務組件的配置,我們只需要添加相應的注解@Scheduled就可以實現任務調度!
三、Schedule 實踐
3.1、pom 包配置
pom包里面只需要引入Spring Boot Starter包即可!
- <dependencies>
- <!--spring boot核心-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <!--spring boot 測試-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
3.2、啟動類啟用定時調度
在啟動類上面加上@EnableScheduling即可開啟定時
- @SpringBootApplication
- @EnableScheduling
- public class ScheduleApplication {
- public static void main(String[] args) {
- SpringApplication.run(ScheduleApplication.class, args);
- }
- }
3.3、創建定時任務
Spring Scheduler支持四種形式的任務調度!
- fixedRate:固定速率執行,例如每5秒執行一次
- fixedDelay:固定延遲執行,例如距離上一次調用成功后2秒執行
- initialDelay:初始延遲任務,例如任務開啟過5秒后再執行,之后以固定頻率或者間隔執行
- cron:使用 Cron 表達式執行定時任務
3.3.1、固定速率執行
你可以通過使用fixedRate參數以固定時間間隔來執行任務,示例如下:
- @Component
- public class SchedulerTask {
- private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * fixedRate:固定速率執行。每5秒執行一次。
- */
- @Scheduled(fixedRate = 5000)
- public void runWithFixedRate() {
- log.info("Fixed Rate Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- }
- }
運行ScheduleApplication主程序,即可看到控制臺輸出效果:
- Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
- Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
- ...
3.3.2、固定延遲執行
你可以通過使用fixedDelay參數來設置上一次任務調用完成與下一次任務調用開始之間的延遲時間,示例如下:
- @Component
- public class SchedulerTask {
- private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * fixedDelay:固定延遲執行。距離上一次調用成功后2秒后再執行。
- */
- @Scheduled(fixedDelay = 2000)
- public void runWithFixedDelay() {
- log.info("Fixed Delay Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- }
- }
控制臺輸出效果:
- Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
- Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:02
- ...
3.3.3、初始延遲任務
你可以通過使用initialDelay參數與fixedRate或者fixedDelay搭配使用來實現初始延遲任務調度。
- @Component
- public class SchedulerTask {
- private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * initialDelay:初始延遲。任務的第一次執行將延遲5秒,然后將以5秒的固定間隔執行。
- */
- @Scheduled(initialDelay = 5000, fixedRate = 5000)
- public void reportCurrentTimeWithInitialDelay() {
- log.info("Fixed Rate Task with Initial Delay,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- }
- }
控制臺輸出效果:
- Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:05
- Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
- ...
3.3.4、使用 Cron 表達式
Spring Scheduler同樣支持Cron表達式,如果以上簡單參數都不能滿足現有的需求,可以使用 cron 表達式來定時執行任務。
關于cron表達式的具體用法,可以點擊參考這里:https://cron.qqe2.com/
- @Component
- public class SchedulerTask {
- private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * cron:使用Cron表達式。每6秒中執行一次
- */
- @Scheduled(cron = "*/6 * * * * ?")
- public void reportCurrentTimeWithCronExpression() {
- log.info("Cron Expression,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- }
- }
控制臺輸出效果:
- Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:06
- Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:12
- ...
3.4、異步執行定時任務
在介紹異步執行定時任務之前,我們先看一個例子!
在下面的示例中,我們創建了一個每隔2秒執行一次的定時任務,在任務里面大概需要花費 3 秒鐘,猜猜執行結果如何?
- @Component
- public class AsyncScheduledTask {
- private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- @Scheduled(fixedRate = 2000)
- public void runWithFixedDelay() {
- try {
- TimeUnit.SECONDS.sleep(3);
- log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- } catch (InterruptedException e) {
- log.error("錯誤信息",e);
- }
- }
- }
控制臺輸入結果:
- Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:26
- Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:31
- Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:36
- Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:41
- ...
很清晰的看到,任務調度頻率變成了每隔5秒調度一次!
這是為啥呢?
從Current Thread : scheduling-1輸出結果可以很看到,任務執行都是同一個線程!默認的情況下,@Scheduled任務都在 Spring 創建的大小為 1 的默認線程池中執行!
更直觀的結果是,任務都是串行執行!
下面,我們將其改成異步線程來執行,看看效果如何?
- @Component
- @EnableAsync
- public class AsyncScheduledTask {
- private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- @Async
- @Scheduled(fixedDelay = 2000)
- public void runWithFixedDelay() {
- try {
- TimeUnit.SECONDS.sleep(3);
- log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
- } catch (InterruptedException e) {
- log.error("錯誤信息",e);
- }
- }
- }
控制臺輸出結果:
- Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-1 : The time is now 2020-12-15 18:55:26
- Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-2 : The time is now 2020-12-15 18:55:28
- Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-3 : The time is now 2020-12-15 18:55:30
- ...
任務的執行頻率不受方法內的時間影響,以并行方式執行!
3.5、自定義任務線程池
雖然默認的情況下,@Scheduled任務都在 Spring 創建的大小為 1 的默認線程池中執行,但是我們也可以自定義線程池,只需要實現SchedulingConfigurer類即可!
自定義線程池示例如下:
- @Configuration
- public class SchedulerConfig implements SchedulingConfigurer {
- @Override
- public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
- ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
- //線程池大小為10
- threadPoolTaskScheduler.setPoolSize(10);
- //設置線程名稱前綴
- threadPoolTaskScheduler.setThreadNamePrefix("scheduled-thread-");
- //設置線程池關閉的時候等待所有任務都完成再繼續銷毀其他的Bean
- threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
- //設置線程池中任務的等待時間,如果超過這個時候還沒有銷毀就強制銷毀,以確保應用最后能夠被關閉,而不是阻塞住
- threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
- //這里采用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務
- threadPoolTaskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- threadPoolTaskScheduler.initialize();
- scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
- }
- }
我們啟動服務,看看cron任務示例調度效果:
- Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 20:46:00
- Cron Expression,Current Thread : scheduled-thread-2,The time is now : 2020-12-15 20:46:06
- Cron Expression,Current Thread : scheduled-thread-3,The time is now : 2020-12-15 20:46:12
- Cron Expression,Current Thread : scheduled-thread-4,The time is now : 2020-12-15 20:46:18
- ....
當前線程名稱已經被改成自定義scheduled-thread的前綴!
四、小結
本文主要圍繞Spring scheduled應用實踐進行分享,如果是單體應用,使用SpringBoot內置的@scheduled注解可以解決大部分業務需求,上手非常容易!
五、參考
1、SpringBoot @Schedule使用與原理分析
原文鏈接:https://mp.weixin.qq.com/s/7J1tlZab2oE-6cm6GZiU_w