前言
在实际的业务开发过程中,我们经常会需要定时任务来帮助我们完成一些工作,例如每天凌晨处理报表数据,每月的月底清除上一个月的交易记录等各种各样的需求。
如果你使用的时SpringBoot
框架,那么这些功能就比较容易实现了。
SpringBoot
帮我们完成了相关定时任务组件的配置,我们只需要添加相应的注解@Secheduled
就可以实现任务调度
实践出真知
POM包配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
启动类修改,开启定时调度及异步线程
@SpringBootApplication
@EnableScheduling //开启定时任务
@EnableAsync //开启异步,默认线程池核心线程数:8
public class ScheduledApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduledApplication.class, args);
}
}
定时任务注解
- fixedRate:固定速率执行,例如每5秒执行一次
- fixedDelay:固定延迟执行,例如距离上一次调用成功后2秒执行
- initialDelay:初始延迟任务,例如任务开启过5秒后再执行,之后以固定频率或者间隔执行
- cron:使用
Cron
表达式执行定时任务
固定速率执行编码
通过使用fixedRate
参数以固定时间间隔来执行任务
@Scheduled(fixedRate = 5000)
public void fixeRateTask(){
System.out.println("Fixed Rate Task,Current Thread : " + Thread.currentThread().getName());
}
固定延迟执行编码
通过使用fixedDelay
参数来设置上一次任务调用完成与下一次任务调用开始之间的延迟时间
@Scheduled(fixedDelay = 2000)
public void fixedDelayTask() throws InterruptedException {
System.out.println("Fixed Delay Task,Current Thread : " + Thread.currentThread().getName());
}
初始延迟任务编码
通过使用initialDelay
参数与fixedRate
或者fixedDelay
搭配使用来实现初始延迟任务调度
@Scheduled(initialDelay = 5000,fixedRate = 5000)
public void initialDelayTask(){
System.out.println("Initial Delay Task,Current Thread : " + Thread.currentThread().getName());
}
Cron表达式编码
Spring Scheduler
同样支持Cron
表达式,如果以上简单参数都不能满足现有的需求,可以使用 cron
表达式来定时执行任务
@Scheduled(cron = "*/6 * * * * ?")
public void cronTask(){
System.out.println("Cron Task,Current Thread : " + Thread.currentThread().getName());
}
关于cron大家可以关注我的另一篇文章:Cron表达式详解
升华
以上的任务调度都是串行运行的,SpringBoot
默认创建一个大小为1的线程池,调度任务统一使用这个线程池执行,如果某个任务耗时过长,就会影响到其他任务的正常执行,这时候我们就会考虑为什么不让任务并行执行呢,
这里我们引入异步线程池@ Async
,它会创建一个线程池核心线程数为8
@Async
@Scheduled(fixedDelay = 2000)
public void fixedDelayTask() throws InterruptedException {
Thread.sleep(20000);
System.out.println("Fixed Delay Task,Current Thread : " + Thread.currentThread().getName() + ",执行时间:" + LocalDateTime.now());
}
修改线程配置信息
- 自定义线程池
ScheduledConfig
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
//线程池大小
taskScheduler.setPoolSize(10);
//设置线程名称前缀
taskScheduler.setThreadNamePrefix("scheduled-thread-");
//设置线程池关闭时等待所有线程任务完成后再销毁其它的bean
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能被关闭
taskScheduler.setAwaitTerminationSeconds(60);
/**
* 采用CallerRunsPolicy策略,当线程池没有处理能力的时候,
* 该策略会直接在execute方法的调用线程中运行被拒绝的任务
* 如果执行程序已关闭,则会丢弃该任务
*/
taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskScheduler.initialize();
//设置默认线程池
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
}
}
运行程序后我们会发现只有串行的调度任务线程名称被修改了,使用的我们的自定义线程池,而使用异步@Async
注解的调度任务并没有使用我们自定义的线程池,这是应为@Async
会自己创建一个线程池,不会使用@Secheduled
的线程池,所以我们可以再自定义一个@Async
的自定义线程池
@Configuration
public class MyAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setThreadNamePrefix("MyTask-thread-");
executor.initialize();
return executor;
}
}
至此有关于Spring scheduled
应用实践进行分享就完成了,不足之处希望大家积极交流。