简介
背景
在项目开发过程中,我们经常需要执行具有周期性的任务。通过定时任务可以很好的帮助我们实现。
我们拿常用的几种定时任务框架做一个比较:
定时任务框架 | Cron表达式 | 固定间隔执行 | 固定频率执行 | 任务持久化 | 开发难易度 |
---|
JDK TimerTask | 不支持 | 支持 | 支持 | 不支持 | 一般 |
Spring Schedule | 支持 | 支持 | 支持 | 不支持 | 简单 |
Quartz | 支持 | 支持 | 支持 | 支持 | 困难 |
优点
基于注解来设置调度器。
非常方便实现简单的调度。
对代码不具有入侵性,非常轻量级。
所以我们会发现,spring schedule 用起来很简单,非常轻量级, 对代码无侵入性, 我们只需要注重业务的编写, 不需要关心如果构造Scheduler。
缺点
一旦调度任务被创建出来, 不能动态更改任务执行周期, 对于复杂的任务调度有一定的局限性。
使用说明
@Component
public class Demo{
@Scheduled(fixedRate = 1000)
public void do(){
doSomething();
}
}
以上是1秒执行一次。
注解详解
spring schedule的核心就是Scheduled注解的使用
public @interface Scheduled {
String cron() default "";
String zone() default "";
long fixedDelay() default -1L;
String fixedDelayString() default "";
long fixedRate() default -1L;
String fixedRateString() default "";
long initialDelay() default -1L;
String initialDelayString() default "";
}
SpringBoot集成schedule
1、添加maven依赖包
由于Spring Schedule包含在spring-boot-starter基础模块中,所有不需要增加额外的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、启动类,添加启动注解
在springboot入口或者配置类中增加@EnableScheduling注解即可启用定时任务。
@EnableScheduling
@SpringBootApplication
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
添加定时任务
我们将对Spring Schedule三种任务调度器分别举例说明。
1.3.1Cron表达式
类似于Linux下的Cron表达式时间定义规则。Cron表达式由6或7个空格分隔的时间字段组成,如下图:
位置 | 时间域名 | 允许值 | 允许的特殊字符 |
---|
1 | 秒 | 0-59 | ,-*/ |
2 | 分钟 | 0-59 | ,-*/ |
3 | 小时 | 0-23 | ,-*/ |
4 | 日期 | 1-31 | ,-*/L W C |
5 | 月份 | 1-12 | ,-*/ |
6 | 星期 | 1-7 | ,-*/L C # |
7 | 年(可选) | 空值 1970-2099 | ,-*/ |
常用表达式:
表达式 | 描述 |
---|
0/30 * * * * * | 每30秒执行一次 |
0 0/5 * * * * | 每5分钟执行一次 |
0 0 0 * * * | 每天凌晨执行 |
0 0 8, 12, 17 * * * | 每天8点、12点、17点整执行 |
0 30 3-5 * * * | 每天3点~5点 30分时执行 |
举个栗子:
添加一个work()方法,每10秒执行一次。
注意:当方法的执行时间超过任务调度频率时,调度器会在下个周期执行。
如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第20秒。
@Component
public class MyTask {
@Scheduled(cron = "0/10 * * * * *")
public void work() {
}
}
1.3.2 固定间隔任务
下一次的任务执行时间,是从方法最后一次任务执行结束时间开始计算。并以此规则开始周期性的执行任务。
举个栗子:
添加一个work()方法,每隔10秒执行一次。
例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第22秒。
@Scheduled(fixedDelay = 1000*10)
public void work() {
}
1.3.3 固定频率任务
按照指定频率执行任务,并以此规则开始周期性的执行调度。
举个栗子:
添加一个work()方法,每10秒执行一次。
注意:当方法的执行时间超过任务调度频率时,调度器会在当前方法执行完成后立即执行下次任务。
例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第12秒。
@Scheduled(fixedRate = 1000*10)
public void work() {
}
配置TaskScheduler线程池
在实际项目中,我们一个系统可能会定义多个定时任务。那么多个定时任务之间是可以相互独立且可以并行执行的。
通过查看org.springframework.scheduling.config.ScheduledTaskRegistrar源代码,发现spring默认会创建一个单线程池。这样对于我们的多任务调度可能会是致命的,当多个任务并发(或需要在同一时间)执行时,任务调度器就会出现时间漂移,任务执行时间将不确定。
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
}
自定义线程池
新增一个配置类,实现SchedulingConfigurer接口。重写configureTasks方法,通过taskRegistrar设置自定义线程池。
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean()
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
taskScheduler.setThreadNamePrefix("xx-task-scheduler-thread-");
taskScheduler.setAwaitTerminationSeconds(30);
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
return taskScheduler;
}
}
使用SpringBoot的@Async来执行多线程并行处理
@Async配置
@Configuration
public class ThreadConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
定时任务使用
@Component
@Slf4j
@Async
public class ScheduleTask {
@Autowired
private XXXTask xxxTask;
@Scheduled(cron = "0 */2 * * * ?")
public void xxxTask(){
log.info("定时任务xxxTask开始时间:"+ DateUtils.currentTime());
try{
xxxTask.xxx();
}catch (WalletException e){
log.error("--------------------------------发生时间{},异常是{}",
DateUtils.currentTime(),e.getResultVoError().getMsg());
}
log.info("定时任务xxxTask结束时间:"+ DateUtils.currentTime());
}
}
参考网站
https://www.cnblogs.com/skychenjiajun/p/9057379.html?utm_source=tuicool&utm_medium=referral
https://www.jianshu.com/p/587901245c95
https://www.cnblogs.com/dolphin0520/p/3932921.html