问题产生的业务背景:
1、三个定时任务,触发时间点都是每周一0点
2、其中一个任务需要基于其他两个任务处理的数据
代码实现:
通过CountDownLatch来实现业务逻辑需要,其中两个先行任务通过手动new的形式来传入依赖的接口和CountDownLatch,另外一个任务就交给sping管理。代码如下:
@Component
@Slf4j
public class DutyWorkTrigger {
@Resource
private IDutyWorkTaskService dutyWorkTaskService;
@Resource
private IDutyWorkExecuteService dutyWorkExecuteService;
@Resource
private DutyEffectEvaluateWorker dutyEffectEvaluateWorker;
@Resource
private HBTThreadPool hbtThreadPool;
private CountDownLatch countDownLatch = new CountDownLatch(2);
// @Scheduled(cron = "0 0 0 ? * MON")
@Scheduled(cron = "0 11 15 * * ?")
public void task() {
hbtThreadPool.getExecutor.execute(new DutyWorkTaskWorker(dutyWorkTaskService, countDownLatch));
}
// @Scheduled(cron = "0 0 0 ? * MON")
@Scheduled(cron = "0 11 15 * * ?")
public void taskExecute() {
hbtThreadPool.getExecutor.execute(new DutyWorkExecuteWorker(dutyWorkExecuteService, countDownLatch));
}
// @Scheduled(cron = "0 0 0 ? * MON")
@Scheduled(cron = "0 11 15 * * ?")
public void evaluate() {
try {
countDownLatch.await();
log.info("运行效果评估任务开始。。。");
hbtThreadPool.getExecutor.execute(dutyEffectEvaluateWorker);
} catch (InterruptedException e) {
log.error("countDownLatch Interrupted", e);
throw new RuntimeException(e);
} finally {
// 定时任务,不会出现并发触发导致的更新countDownLatch错位
countDownLatch = new CountDownLatch(2);
}
}
那么问题来了:
启动后,发现任务不执行,没有打任何日志(先行的两个worker中有打日志)
分析:
经过调试后发现,到点触发时,先执行了第三个任务,countDownLatch.await()停止了,先行的两个任务不执行了,也就不会调用countDownLatch.countDown()进行计数减一,结果导致第三个任务就一直等待中。那么为什么两个先行的任务不行了?那么遵循,遇到问题先网上搜一波的原则,我找到了答案。
原因:
spring定时任务框架默认是单线程,只有前面一个任务执行完后,才会继续进行下一个任务,那么这边三个任务是相同的触发时间,而恰好第三个任务排在第一个执行,但因为被countDownLatch.await()住,在等待其他两个任务执行进行计数减一,而那两个任务却因为定时任务框架的机制在等待第三个任务执行结束,死循环。。。
解决:
问题的根本原因是,定时任务框架是单线程的,不能并行执行相同时间点的任务。要解决,只要把单线程的改成多线程的,而spring也是支持的,代码如下,加个配置,创建自定义的定时任务线程池,而不是用默认的。
@Configuration
public class ScheduledTaskConfig {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(3); // 线程池大小
threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-"); // 线程名称
threadPoolTaskScheduler.setAwaitTerminationSeconds(60); // 等待时长
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); // 调度器shutdown被调用时等待当前被调度的任务完成
return threadPoolTaskScheduler;
}
}
注意:我这边是在启动类上加的@EnableScheduling注解