一、问题描述
项目中定时器推送任务有一个设置5秒推送一次消息,有时会失效,使用@Scheduled(cron=“* 0/5 * * * ?”)来定时推送,这种定时任务默认是单线程执行的,而程序中有三个定时任务,可能会发生线程抢占的现象,所以要将定时任务设置成多线程的方式。
二、场景复现
项目描述:使用Springboot进行开发
设置两个定时任务,每5s执行一次,并打印出其执行情况
代码如下:
@Component
@Log4j2
public class ScheduledTask {
@Scheduled(cron = "0/5 * * * * ?")
public void task1() throws InterruptedException {
log.info("I am task11111111, current thread: {}", Thread.currentThread());
while (true) {
//模拟耗时任务,阻塞10s
Thread.sleep(10000);
break;
}
}
@Scheduled(cron = "0/5 * * * * ?")
public void task2() {
log.info("I am task22222222, current thread: {}", Thread.currentThread());
}
}
执行结果:
由于任务1阻塞了10s,导致本应5s执行一次的定时任务10s才执行一次。
三、解决方案
方案一:使用@Async注解实现异步任务
这种方式比较简单,在定时任务上加上@Async注解,注意:需启动类配合加上 @EnableAsync才会生效
@Component
@Log4j2
public class ScheduledTask {
@Async
@Scheduled(cron = "0/5 * * * * ?")
public void task1() throws InterruptedException {
log.info("I am task11111111, current thread: {}", Thread.currentThread());
while (true) {
//模拟耗时任务,阻塞10s
Thread.sleep(10000);
break;
}
}
@Async
@Scheduled(cron = "0/5 * * * * ?")
public void task2() {
log.info("I am task22222222, current thread: {}", Thread.currentThread());
}
}
方案二:手动设置定时任务的线程池大小
定时任务代码部分还原,不使用@Async注解,新增启动代码配置:
@Configuration
public class AppConfig implements SchedulingConfigurer {
@Bean
public Executor taskExecutor() {
//指定定时任务线程数量,可根据需求自行调节
return Executors.newScheduledThreadPool(3);
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(taskExecutor());
}
}
注意:
有个坑,从日志上看@Async方式针对同一任务也是异步的,也即task1每5s会执行一次,但是方式二貌似对同一个任务不会生效,task1执行的时候需等待上一次执行结束才会触发,并没有每5s执行一次。