Spring之定时任务和异步任务
Spring之定时任务和异步任务
一、定时任务
Spring框架下使用@Scheduled、@EnableScheduling两个注解可以快速开发定时器。
用法
在需要定时执行的方法上使用@Scheduled
在所属类上使用@EnableScheduling
定时规则
@Scheduled可以指定配置定时器的执行规则,参数如下。
- cron
参数接收一个cron表达式,cron表达式是一个以空格为间隔符来区分不同域的字符串,总共有6个或7个域。cron表达式从左到右每个域分别标识的[秒] [分] [小时] [日] [月] [周] [年]
,其中[年]不是必选的域可以省略。
序号 | 说明 | 必填 | 可选值 | 允许的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 时 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 / JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 / SUN-SAT | , - * ? / L # |
7 | 年 | 否 | 1970-2099 | , - * / |
通配符说明:
*
表示所有值。 例如:在时的字段上设置 *,表示每一个小时都会触发
?
表示不指定值,即当前使用的场景为不需要关心这个字段设置的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为“?” 具体设置为 0 0 0 10 * ?
-
表示区间。例如:在小时上设置 “10-12”,表示 10,11,12点都会触发
,
表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/
用于递增触发。如在秒上面设置“5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。在日字段上设置‘1/3’所示每月1号开始,每隔三天触发一次
示例
@Scheduled(cron = */5 * * * * ?) // 每隔5秒执行一次
@Scheduled(cron = 0 */1 * * * ?) // 每隔1分钟执行一次
@Scheduled(cron = 0 0 0 * * ?) // 每天0点执行一次
@Scheduled(cron = 0 0 1 1 * ?) // 每月1号凌晨1点执行一次
@Scheduled(cron = 0 0 0,12,18 * * ?) // 每天的0点、12点、18点各执行一次
- fixedDelay
上一次执行完成后延迟多久执行下一次,单位ms
@Scheduled(fixedDelay = 60000) // 上一次执行完成后延迟1min再执行
- fixedRate
固定延迟多久执行下一次任务,不依赖于上一次任务执行成功的时间,单位ms
@Scheduled(fixedRate = 60000) // 上一次执行后延迟1min执行
- initialDelay
启动后延迟多久后执行第一次,可根据场景搭配fixedRate或fixedDelay实现定时调度
@Scheduled(initialDelay = 5000,fixedRate= 60000) //启动后延迟5s执行,之后每次执行时间间隔1min
示例
@EnableScheduling
public class ScheduleTaskFunc {
// 每天0点定时清理
@Scheduled(cron = "0 0 0 * * ?")
public void scheduleTaskMethod() {
// method body
}
}
二、异步任务
使用@EnableAsync和@Async来实现。
用法
在需要定时执行的方法上使用@Async
,若在类上使用则表示该类所有方法都是异步方法
在所属类上使用@EnableAsync
要求:
@Async注解的方法必须是public的方法,不能是private的方法
使用此注解的方法的类对象,必须是spring管理下的bean对象 (如被@Service、@Component等修饰的Bean对象)
默认线程池和自定义线程池
- 默认线程池:使用@Async()注解时,若不指定线程池名称,则使用Spring默认的线程池,为SimpleAsyncTaskExecutor。该线程池默认8个核心线程,可通过修改application.yaml配置文件进行修改。
- 自定义线程池:@Configuration + @Bean(name = “线程池名称”) + @Async(“线程池名”)
- 定义一个异步类和执行器(可在此处指定核心线程数,最大线程数,队列大小等规格)
- 使用@Async(“线程池名”)注解来指定使用这个自定义线程池
三、配合使用示例
(1)编写自定义线程池
@Configuration
@EnableAsync
public class AsyncTaskPool {
@Bean(name = "asyncTaskExecutor")
public Executor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4); // 核心线程数
executor.setMaxPoolSize(8); // 最大线程数
executor.setQueueCapacity(100); // 队列大小
executor.setKeepAliveSeconds(60); // 线程空闲时的存活时间
executor.setThreadNamePrefix("MyThreadPoolTaskExecutor-"); // 线程名前缀
executor.setRejectedExecutionHandler((ThreadPoolExecutor.AbortPolicy) -> {
// 拒绝策略,例如记录日志或者抛出异常
});
executor.initialize(); // 初始化线程池
return executor;
}
}
(2)编写异步定时任务
@Component
@EnableAsync
@EnableScheduling
public class asyncScheduledFunc {
@Async("asyncTaskExecutor")
@Scheduled(cron = "0 0 0 * * ?")
@SchedulerLock(name = "oneSchedulerLock", lockAtMostFor = "PT600S", lockAtLeastFor = "PT60S")
public void asyncScheduledTask() {
// method body
}
}
补充:@SchedulerLock
@SchedulerLock为分布式锁,当服务为多节点部署且使用了@Scheduled编写定时任务,那么在同一时刻所有节点会同时执行定时任务,但我们可能希望只需要一个节点执行,这时就需要使用分布式锁控制执行过程。
参数:
- name:任务唯一标识。同一个name的任务,同一个时刻,多个线程只会有一个线程获取到锁。其他没有获取到锁的线程会跳过,不会阻塞等待。
- lockAtLeastFor:持有锁的最短时间。这个主要是防止不同节点时间存在误差。
- lockAtMostFor:持有锁的最长时间。主要是为了防止死锁。当一个任务执行完成时会释放锁,当一个任务执行超过lockAtMostFor时间时,也会释放锁。这个时间要大于业务执行的时间,不然一个任务可能会被执行多次。