SpringBoot中的定時(shí)調(diào)度服務(wù)使用詳解
一、背景介紹
在實(shí)際的業(yè)務(wù)開(kāi)發(fā)過(guò)程中,我們經(jīng)常會(huì)需要定時(shí)任務(wù)來(lái)幫助我們完成一些工作,例如每天早上 6 點(diǎn)生成銷(xiāo)售報(bào)表、每晚 23 點(diǎn)清理臟數(shù)據(jù)等等。

如果你當(dāng)前使用的是 SpringBoot 來(lái)開(kāi)發(fā)項(xiàng)目,那么完成這些任務(wù)會(huì)非常容易!
SpringBoot 默認(rèn)已經(jīng)幫我們完成了相關(guān)定時(shí)任務(wù)組件的配置,我們只需要添加相應(yīng)的注解@Scheduled就可以實(shí)現(xiàn)任務(wù)調(diào)度!
二、方案實(shí)踐
2.1、pom 包配置
pom包里面只需要引入Spring Boot Starter包即可!
<dependencies>
<!--spring boot核心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring boot 測(cè)試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>2.2、啟動(dòng)類(lèi)啟用定時(shí)調(diào)度
在啟動(dòng)類(lèi)上面加上@EnableScheduling即可開(kāi)啟定時(shí)
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
2.3、創(chuàng)建定時(shí)任務(wù)
Spring Scheduler支持四種形式的任務(wù)調(diào)度!
- fixedRate:固定速率執(zhí)行,例如每5秒執(zhí)行一次
- fixedDelay:固定延遲執(zhí)行,例如距離上一次調(diào)用成功后2秒執(zhí)行
- initialDelay:初始延遲任務(wù),例如任務(wù)開(kāi)啟過(guò)5秒后再執(zhí)行,之后以固定頻率或者間隔執(zhí)行
- cron:使用 Cron 表達(dá)式執(zhí)行定時(shí)任務(wù)
2.3.1、固定速率執(zhí)行
你可以通過(guò)使用fixedRate參數(shù)以固定時(shí)間間隔來(lái)執(zhí)行任務(wù),示例如下:
@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:固定速率執(zhí)行。每5秒執(zhí)行一次。
*/
@Scheduled(fixedRate = 5000)
public void runWithFixedRate() {
log.info("Fixed Rate Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}
運(yùn)行ScheduleApplication主程序,即可看到控制臺(tái)輸出效果:
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 ...
2.3.2、固定延遲執(zhí)行
你可以通過(guò)使用fixedDelay參數(shù)來(lái)設(shè)置上一次任務(wù)調(diào)用完成與下一次任務(wù)調(diào)用開(kāi)始之間的延遲時(shí)間,示例如下:
@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:固定延遲執(zhí)行。距離上一次調(diào)用成功后2秒后再執(zhí)行。
*/
@Scheduled(fixedDelay = 2000)
public void runWithFixedDelay() {
log.info("Fixed Delay Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}
控制臺(tái)輸出效果:
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 ...
2.3.3、初始延遲任務(wù)
你可以通過(guò)使用initialDelay參數(shù)與fixedRate或者fixedDelay搭配使用來(lái)實(shí)現(xiàn)初始延遲任務(wù)調(diào)度。
@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:初始延遲。任務(wù)的第一次執(zhí)行將延遲5秒,然后將以5秒的固定間隔執(zhí)行。
*/
@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()));
}
}
控制臺(tái)輸出效果:
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 ...
2.3.4、使用 Cron 表達(dá)式
Spring Scheduler同樣支持Cron表達(dá)式,如果以上簡(jiǎn)單參數(shù)都不能滿足現(xiàn)有的需求,可以使用 cron 表達(dá)式來(lái)定時(shí)執(zhí)行任務(wù)。
關(guān)于cron表達(dá)式的具體用法,可以點(diǎn)擊參考這里: 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表達(dá)式。每6秒中執(zhí)行一次
*/
@Scheduled(cron = "*/6 * * * * ?")
public void reportCurrentTimeWithCronExpression() {
log.info("Cron Expression,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}
控制臺(tái)輸出效果:
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 ...
2.4、異步執(zhí)行定時(shí)任務(wù)
在介紹異步執(zhí)行定時(shí)任務(wù)之前,我們先看一個(gè)例子!
在下面的示例中,我們創(chuàng)建了一個(gè)每隔2秒執(zhí)行一次的定時(shí)任務(wù),在任務(wù)里面大概需要花費(fèi) 3 秒鐘,猜猜執(zhí)行結(jié)果如何?
@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("錯(cuò)誤信息",e);
}
}
}
控制臺(tái)輸入結(jié)果:
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 ...
很清晰的看到,任務(wù)調(diào)度頻率變成了每隔5秒調(diào)度一次!
這是為啥呢?
從Current Thread : scheduling-1輸出結(jié)果可以很看到,任務(wù)執(zhí)行都是同一個(gè)線程!默認(rèn)的情況下,@Scheduled任務(wù)都在 Spring 創(chuàng)建的大小為 1 的默認(rèn)線程池中執(zhí)行!
更直觀的結(jié)果是,任務(wù)都是串行執(zhí)行!
下面,我們將其改成異步線程來(lái)執(zhí)行,看看效果如何?
@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("錯(cuò)誤信息",e);
}
}
}
控制臺(tái)輸出結(jié)果:
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 ...
任務(wù)的執(zhí)行頻率不受方法內(nèi)的時(shí)間影響,以并行方式執(zhí)行!
2.5、自定義任務(wù)線程池
雖然默認(rèn)的情況下,@Scheduled任務(wù)都在 Spring 創(chuàng)建的大小為 1 的默認(rèn)線程池中執(zhí)行,但是我們也可以自定義線程池,只需要實(shí)現(xiàn)SchedulingConfigurer類(lèi)即可!
自定義線程池示例如下:
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
//線程池大小為10
threadPoolTaskScheduler.setPoolSize(10);
//設(shè)置線程名稱(chēng)前綴
threadPoolTaskScheduler.setThreadNamePrefix("scheduled-thread-");
//設(shè)置線程池關(guān)閉的時(shí)候等待所有任務(wù)都完成再繼續(xù)銷(xiāo)毀其他的Bean
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//設(shè)置線程池中任務(wù)的等待時(shí)間,如果超過(guò)這個(gè)時(shí)候還沒(méi)有銷(xiāo)毀就強(qiáng)制銷(xiāo)毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
//這里采用了CallerRunsPolicy策略,當(dāng)線程池沒(méi)有處理能力的時(shí)候,該策略會(huì)直接在 execute 方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù);如果執(zhí)行程序已關(guān)閉,則會(huì)丟棄該任務(wù)
threadPoolTaskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskScheduler.initialize();
scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}
我們啟動(dòng)服務(wù),看看cron任務(wù)示例調(diào)度效果:
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 ....
當(dāng)前線程名稱(chēng)已經(jīng)被改成自定義scheduled-thread的前綴!
三、總結(jié)
本文主要圍繞Spring scheduled應(yīng)用實(shí)踐進(jìn)行分享,如果是單體應(yīng)用,使用SpringBoot內(nèi)置的@scheduled注解可以解決大部分業(yè)務(wù)需求,上手非常容易!
項(xiàng)目源代碼地址:spring-boot-example-schedule
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 基于Nacos實(shí)現(xiàn)SpringBoot動(dòng)態(tài)定時(shí)任務(wù)調(diào)度
- 基于SpringBoot實(shí)現(xiàn)輕量級(jí)的動(dòng)態(tài)定時(shí)任務(wù)調(diào)度的方法
- SpringBoot整合PowerJob實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度
- SpringBoot定時(shí)調(diào)度之Timer與Quartz詳解
- SpringBoot定時(shí)任務(wù)調(diào)度與爬蟲(chóng)的配置實(shí)現(xiàn)
- SpringBoot2 task scheduler 定時(shí)任務(wù)調(diào)度器四種方式
相關(guān)文章
Spring Boot整合ElasticSearch實(shí)現(xiàn)多版本兼容的方法詳解
簡(jiǎn)單說(shuō),ElasticSearch(簡(jiǎn)稱(chēng) ES)是搜索引擎,是結(jié)構(gòu)化數(shù)據(jù)的分布式搜索引擎。下面這篇文章主要給大家介紹了關(guān)于Spring Boot整合ElasticSearch實(shí)現(xiàn)多版本兼容的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧2018-05-05
java實(shí)現(xiàn)ip地址與十進(jìn)制數(shù)相互轉(zhuǎn)換
本文介紹在java中IP地址轉(zhuǎn)換十進(jìn)制數(shù)及把10進(jìn)制再轉(zhuǎn)換成IP地址的方法及實(shí)例參考,曬出來(lái)和大家分享一下2012-12-12
SpringBoot項(xiàng)目創(chuàng)建使用+配置文件+日志文件詳解
Spring的出現(xiàn)是為了簡(jiǎn)化 Java 程序開(kāi)發(fā),而 SpringBoot 的出現(xiàn)是為了簡(jiǎn)化 Spring 程序開(kāi)發(fā),這篇文章主要介紹了SpringBoot項(xiàng)目創(chuàng)建使用+配置文件+日志文件,需要的朋友可以參考下2023-02-02
深入了解JVM(Java虛擬機(jī))內(nèi)存結(jié)構(gòu)
Java虛擬機(jī)(Java Virtual Machine,JVM)是Java程序的運(yùn)行環(huán)境,它是一個(gè)抽象的計(jì)算機(jī)模型,通過(guò)解釋和執(zhí)行Java字節(jié)碼來(lái)運(yùn)行Java程序,本將大家深入了解JVM(Java虛擬機(jī))內(nèi)存結(jié)構(gòu),需要的朋友可以參考下2023-08-08
詳解@Autowired(required=false)注入注意的問(wèn)題
這篇文章主要介紹了@Autowired(required=false)注入注意的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
SpringBoot+JSON+AJAX+ECharts+Fiddler實(shí)現(xiàn)前后端分離開(kāi)發(fā)可視化
這篇文章主要介紹了SpringBoot+JSON+AJAX+ECharts+Fiddler實(shí)現(xiàn)前后端分離開(kāi)發(fā)可視化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Python文件高級(jí)操作函數(shù)之文件信息獲取與目錄操作
這篇文章主要介紹了Python文件高級(jí)操作函數(shù)之文件信息獲取與目錄操作,在Python中,內(nèi)置了文件(File)對(duì)象。在使用文件對(duì)象時(shí),首先需要通過(guò)內(nèi)置的open()方法創(chuàng)建一個(gè)文件對(duì)象,然后通過(guò)該對(duì)象提供的方法進(jìn)行一些基本文件操作,需要的朋友可以參考下2023-05-05
SpringKafka錯(cuò)誤處理(重試機(jī)制與死信隊(duì)列)
Spring Kafka提供了全面的錯(cuò)誤處理機(jī)制,通過(guò)靈活的重試策略和死信隊(duì)列處理,下面就來(lái)介紹一下,具有一定的參考價(jià)值,感興趣的可以了解一下2025-04-04

