定時任務(wù)調(diào)度功能在我們的開發(fā)中是非常常見的,隨便舉幾個例子:定時清除一些過期的數(shù)據(jù),定時發(fā)送郵件等等,實現(xiàn)定時任務(wù)調(diào)度的方式也十分多樣,本篇文章主要學(xué)習(xí)各種實現(xiàn)定時任務(wù)調(diào)度方式的優(yōu)缺點,以便為日后選擇的時候提供一定的參考。
本篇要點
- 介紹Timer實現(xiàn)定時任務(wù)。
- 介紹ScheduledExecutorService實現(xiàn)定時任務(wù)。
- 介紹SpringBoot使用SpringTask實現(xiàn)定時任務(wù)。
- 介紹SpringBoot使用SpringTask實現(xiàn)異步任務(wù)。
Timer實現(xiàn)定時任務(wù)
基于JDK自帶的JAVA.util.Timer,通過調(diào)度java.util.TimeTask讓某一段程序按某一固定間隔,在某一延時之后定時執(zhí)行。
缺點:
- 無法指定某一時間的時候執(zhí)行。
- 存在潛在bug,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務(wù)便會自動終止運行。
public class DemoTimer {
//延時時間
private static final long DELAY = 3000;
//間隔時間
private static final long PERIOD = 5000;
public static void main(String[] args) {
// 定義要執(zhí)行的任務(wù)
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任務(wù)執(zhí)行 --> " + LocalDateTime.now());
}
};
Timer timer = new Timer();
timer.schedule(task, DELAY, PERIOD);
}
}
ScheduledExecutorService實現(xiàn)定時任務(wù)
阿里巴巴開發(fā)規(guī)范明確規(guī)定:希望開發(fā)者使用ScheduledExecutorService代替Timer。
多線程并行處理定時任務(wù)時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務(wù)便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
public class DemoScheduledExecutorService {
//延時時間
private static final long DELAY = 3000;
//間隔時間
private static final long PERIOD = 5000;
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("任務(wù)執(zhí)行 --> " + LocalDateTime.now());
}
};
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(task, DELAY, PERIOD, TimeUnit.MILLISECONDS);
}
}
SpringBoot使用Spring Task實現(xiàn)定時任務(wù)
自動配置實現(xiàn)原理
Spring為我們提供了異步執(zhí)行任務(wù)調(diào)度的方式,提供TaskExecutor,TaskScheduler接口,而SpringBoot的自動配置類org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration為我們默認(rèn)注入了他們的實現(xiàn):ThreadPoolTaskScheduler,本質(zhì)上是ScheduledExecutorService 的封裝,增強(qiáng)在調(diào)度時間上的功能。
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class TaskSchedulingAutoConfiguration {
@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
return builder.build();
}
@Bean
@ConditionalOnMissingBean
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
builder = builder.poolSize(properties.getPool().getSize());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskSchedulerCustomizers);
return builder;
}
}
新建工程,引入依賴
Spring Task是Spring Framework中的模塊,我們只需引入spring-boot-starter依賴就可以了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
編寫配置類@EnableScheduling
@Configuration
@EnableScheduling
public class ScheduleConfig {
}
- @Configuration表明這是個配置類。
- @EnableScheduling表明啟用Spring的定時任務(wù)調(diào)度功能。
定義定時任務(wù)@Scheduled
@Component
@Slf4j
public class DemoTask {
private final AtomicInteger counts = new AtomicInteger();
@Scheduled(cron = "0/5 * * * * *")
public void execute() {
log.info("[定時任務(wù)第 {} 次執(zhí)行]", counts.incrementAndGet());
}
}
- @Component表明該類需要被掃描,以便于Spring容器管理。
- @Scheduled標(biāo)注需要調(diào)度執(zhí)行的方法,定義執(zhí)行規(guī)則,其必須指定cron、fixedDelay或fixedRate三個屬性其中一個。cron:定義Spring cron表達(dá)式,網(wǎng)上有在線cron生成器,可以對照著編寫符合需求的定時任務(wù)。fixedDelay :固定執(zhí)行間隔,單位:毫秒。注意,以調(diào)用完成時刻為開始計時時間。fixedRate :固定執(zhí)行間隔,單位:毫秒。注意,以調(diào)用開始時刻為開始計時時間。
主啟動類
@SpringBootApplication
public class SpringBootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTaskApplication.class, args);
}
}
定義配置文件
Spring Task 調(diào)度任務(wù)的配置,對應(yīng) TaskSchedulingProperties 配置類。SpringBoot允許我們在yml或properties定制這些外部化配置,如果不配置也是沒有關(guān)系的,自動配置已經(jīng)給你一套默認(rèn)的值了。
spring:
task:
scheduling:
thread-name-prefix: summerday- # 線程池的線程名的前綴。默認(rèn)為 scheduling- ,建議根據(jù)自己應(yīng)用來設(shè)置
pool:
size: 10 # 線程池大小。默認(rèn)為 1 ,根據(jù)自己應(yīng)用來設(shè)置
shutdown:
await-termination: true # 應(yīng)用關(guān)閉時,是否等待定時任務(wù)執(zhí)行完成。默認(rèn)為 false ,建議設(shè)置為 true
await-termination-period: 60 # 等待任務(wù)完成的最大時長,單位為秒。默認(rèn)為 0 ,根據(jù)自己應(yīng)用來設(shè)置
啟動項目測試
# 初始化一個 ThreadPoolTaskScheduler 任務(wù)調(diào)度器
2020-11-30 23:04:51.886 INFO 10936 --- [ restartedMain] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
# 每5s執(zhí)行一次定時任務(wù)
2020-11-30 23:04:55.002 INFO 10936 --- [ summerday-1] com.hyh.task.DemoTask : [定時任務(wù)第 1 次執(zhí)行]
2020-11-30 23:05:00.002 INFO 10936 --- [ summerday-1] com.hyh.task.DemoTask : [定時任務(wù)第 2 次執(zhí)行]
2020-11-30 23:05:05.002 INFO 10936 --- [ summerday-2] com.hyh.task.DemoTask : [定時任務(wù)第 3 次執(zhí)行]
2020-11-30 23:05:10.001 INFO 10936 --- [ summerday-1] com.hyh.task.DemoTask : [定時任務(wù)第 4 次執(zhí)行]
2020-11-30 23:05:15.002 INFO 10936 --- [ summerday-3] com.hyh.task.DemoTask : [定時任務(wù)第 5 次執(zhí)行]
SpringTask異步任務(wù)
SpringTask除了@Scheduled、@EnableScheduling同步定時任務(wù)之外,還有@Async、@EnableAsync 開啟異步的定時任務(wù)調(diào)度。
SpringBoot自動配置類對異步的支持:org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
@Async注解添加
@Async
@Scheduled(cron = "0/1 * * * * *")
public void asyncTask() {
sleep();
System.out.println(Thread.currentThread().getName() + " async-task 執(zhí)行,當(dāng)前時間: " + LocalDateTime.now());
}
@EnableAsync注解添加
@Configuration
@EnableScheduling // 同步
@EnableAsync // 異步
public class ScheduleConfig {
}
配置文件
spring:
task:
# Spring 執(zhí)行器配置,對應(yīng) TaskExecutionProperties 配置類。對于 Spring 異步任務(wù),會使用該執(zhí)行器。
execution:
thread-name-prefix: async- # 線程池的線程名的前綴。默認(rèn)為 task- ,建議根據(jù)自己應(yīng)用來設(shè)置
pool: # 線程池相關(guān)
core-size: 8 # 核心線程數(shù),線程池創(chuàng)建時候初始化的線程數(shù)。默認(rèn)為 8 。
max-size: 20 # 最大線程數(shù),線程池最大的線程數(shù),只有在緩沖隊列滿了之后,才會申請超過核心線程數(shù)的線程。默認(rèn)為 Integer.MAX_VALUE
keep-alive: 60s # 允許線程的空閑時間,當(dāng)超過了核心線程之外的線程,在空閑時間到達(dá)之后會被銷毀。默認(rèn)為 60 秒
queue-capacity: 200 # 緩沖隊列大小,用來緩沖執(zhí)行任務(wù)的隊列的大小。默認(rèn)為 Integer.MAX_VALUE 。
allow-core-thread-timeout: true # 是否允許核心線程超時,即開啟線程池的動態(tài)增長和縮小。默認(rèn)為 true 。
shutdown:
await-termination: true # 應(yīng)用關(guān)閉時,是否等待定時任務(wù)執(zhí)行完成。默認(rèn)為 false ,建議設(shè)置為 true
await-termination-period: 60 # 等待任務(wù)完成的最大時長,單位為秒。默認(rèn)為 0 ,根據(jù)自己應(yīng)用來設(shè)置
同步與異步對比
@Component
public class DemoAsyncTask {
@Scheduled(cron = "0/1 * * * * *")
public void synTask() {
sleep();
System.out.println(Thread.currentThread().getName() + " syn-task 執(zhí)行,當(dāng)前時間: " + LocalDateTime.now());
}
@Async
@Scheduled(cron = "0/1 * * * * *")
public void asyncTask() {
sleep();
System.out.println(Thread.currentThread().getName() + " async-task 執(zhí)行,當(dāng)前時間: " + LocalDateTime.now());
}
private void sleep() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同時開啟同步和異步任務(wù),假設(shè)任務(wù)本身耗時較長,且間隔較短:間隔1s,執(zhí)行10s,同步與異步執(zhí)行的差異就此體現(xiàn)。
可以看到,同步任務(wù)并沒有每間隔1s就執(zhí)行,而是串行在一起,等前一個任務(wù)執(zhí)行完才執(zhí)行。而異步任務(wù)則不一樣,成功將串行化的任務(wù)并行化。
原文鏈接:https://www.cnblogs.com/summerday152/p/14070941.html
如果覺得本文對你有幫助,可以轉(zhuǎn)發(fā)關(guān)注支持一下






