项目场景:
有一个需求,需要同步大量数据,使用定时任务定期从外部系统获取数据同步到系统中,初次同步数据量会比较大,为了加快数据同步的效率,(之前也没有实际用过线程池,顺便上手用一下),于是希望在定时任务中使用线程池多线程去执行任务。
问题描述
在ScheduleService
类中,用@Scheduled
修饰定时任务需要执行的方法syncDataSchedule
@Component
public class ScheduleService {
@Scheduled(fixedDelay = 60000, initialDelay = 5000)
public void syncDkxData() {
log.warn("{}开始同步数据定时任务", DateUtils.getTime());
List list = getSyncIds();
list.forEach(i -> {
syncData(i);
})
log.warn("{}结束同步数据定时任务", DateUtils.getTime());
}
@Async("threadPool")
public void syncData(int i) {
// 业务代码
}
}
有一个自定义的线程池的类AsyncServiceExecutor
@EnableAsync
@Configuration
public class AsyncServiceExecutor {
@Bean("threadPool")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程池的一些配置
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
使用以上的代码执行后,发现syncData
方法并没有通过自定义的threadPool
线程池去执行,还是用的定时任务主线程在执行。
原因分析:
@Async
有几种失效场景,其中一种是内部方法调用,Spring通过@Async注解实现异步的功能,底层其实是通过Spring的AOP实现的,也就是说它需要通过JDK动态代理或者cglib,生成代理对象。
解决方案:
把syncData
方法写到另一个类SyncDataService
中,再通过syncDataService.syncData()
去调用。
@Component
public class ScheduleService {
@Resource SyncDataService syncDataService;
@Scheduled(fixedDelay = 60000, initialDelay = 5000)
public void syncDkxData() {
log.warn("{}开始同步数据定时任务", DateUtils.getTime());
List list = getSyncIds();
list.forEach(i -> {
syncDataService.syncData(i);
})
log.warn("{}结束同步数据定时任务", DateUtils.getTime());
}
}
@Service
public class SyncDataService{
@Async("threadPool")
public void syncData(int i) {
// 业务代码
}
}
后续完善
为了防止前一次定时任务没有执行完,就开始执行下一次定时任务,在定时任务主线程中使用了CountDownLatch
来阻塞定时任务主线程,直到所有任务执行完成后再结束,开始下一轮定时任务的执行。
还在syncData
方法上加了@Transactional
来支持事务回滚。
但是后面经过的考虑,发现本身的设计逻辑存在问题:
- 使用定时任务来执行本次数据同步的任务,本身就是因为要求的实时性不高,是允许任务执行的时间比较长的,使用线程池虽然能使用多线程加快执行,但是我又加了
CountDownLatch
来阻塞主线程,感觉目的乱了,没有实际意义。又其实只有第一次执行的时候会有较多的数据需要同步,后续都是增量同步,大数据量是少数的情况,所以还是放弃了使用线程池,仍旧采用定时任务单个线程去执行。(这次的使用也算是一个练习,踩到了@Async
的坑,整体还是有收获的) - 后面又加上的
@Transactional
注解,在有些文章上看到是和@Async
注解存在冲突的,会使事务的回滚失效,但是在测试的时候并没有发现事务回滚失效,出现异常数据库能够正常回滚,后续还需要再去验证。
新知识
@Async注解失效场景
- 没有使用
@EnableAsync
注解 - 内部方法调用,没有走Spring的代理
- 非
public
方法 - 相关方法的返回值必须是
void
或者Future
,否则失效 - 方法用
static
或者final
修饰 - 业务类没有被Spring管理,例如没加
@Service
之类的注解,自己new的对象 - 使用
@Transactional
与@Async
同时注解方法,导致事务失效 - 线程池配置有误