@Scheduled阻塞导致未执行生效的情况分析及解决方案
@Scheduled阻塞导致未执行生效的情况分析
今天排查线上数据,发现数据并未更新,查看日志发现更新数据的定时任务并没有执行,而执行该定时任务的时间发现执行了另外的定时任务,所以因此初步判断可能是定时任务阻塞导致相同时间的定时任务有未执行任务。
写了个DEMO果真复现了,@Scheduled注解的定时任务为单线程执行,所以必定会有阻塞情况。
测试代码
- 定时任务【1】DEMO代码
@Component
public class Test01 {
// 每秒执行一次
@Scheduled(cron = "0/1 * * * * ? ")
public void test() {
// 时分秒
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
// 日志打印
System.out.println(nowTime + " 任务【1】执行 线程:" + Thread.currentThread().getName());
}
}
- 定时任务【2】DEMO代码
@Component
public class Test02 {
// 每5秒执行一次
@Scheduled(cron = "0/5 * * * * ? ")
public void test() {
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(nowTime + " 任务【2】执行 线程:" + Thread.currentThread().getName());
try {
// 模拟耗时任务,阻塞2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
服务启动后运行结果
原因分析
- 由程序运行后的打印结果可以发现两个任务均是使用的同一线程。
- 任务【1】应该是每秒执行一次的,但是执行时间上可以发现,任务【1】的执行时间不是连续的,这就说明有时间点是任务【1】没有执行的,而这些时间点恰好任务【2】正在执行中,单线程的原因线程此时阻塞,从而导致这些时间点任务【1】没有执行。
解决方案
- 添加@Async注解或者使用自定义线程池执行任务
代码这里就不贴了,就是在上面的任务【1】和任务【2】的@Scheduled注解上面添加一个注解@Async即可。
多线程执行定时任务后的执行结果
很明显,任务【1】每秒执行的时间连续了!!!没有未执行的情况。
但需要注意的是,可以对@Async进行自定义配置,使其使用时,内部也是通过创建ThreadPoolExecutor线程池来执行。
- 【推荐】一劳永逸,统一配置:实现SchedulingConfigurer接口
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
// 自定义调度器,设置为一个支持定时及周期性的任务执行的线程池,这里初始3个线程
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
}
}
统一配置后便不需要添加@Async注解或者创建线程池来达到多线程了
执行结果
很明显,定时任务执行的线程达到多线程执行,任务【1】执行时间连续,没有出现未执行的情况,且线程也是反复使用的三个。
查看setScheduler()方法源码:
由源码也可以看出,自定义调度器的时候,只能设置TaskScheduler
和ScheduledExecutorService
类型,除此之外的类型都会报错。