记一次在springboot项目中使用@Async线程池不生效的问题

项目场景:

有一个需求,需要同步大量数据,使用定时任务定期从外部系统获取数据同步到系统中,初次同步数据量会比较大,为了加快数据同步的效率,(之前也没有实际用过线程池,顺便上手用一下),于是希望在定时任务中使用线程池多线程去执行任务。


问题描述

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来支持事务回滚。
但是后面经过的考虑,发现本身的设计逻辑存在问题:

  1. 使用定时任务来执行本次数据同步的任务,本身就是因为要求的实时性不高,是允许任务执行的时间比较长的,使用线程池虽然能使用多线程加快执行,但是我又加了CountDownLatch来阻塞主线程,感觉目的乱了,没有实际意义。又其实只有第一次执行的时候会有较多的数据需要同步,后续都是增量同步,大数据量是少数的情况,所以还是放弃了使用线程池,仍旧采用定时任务单个线程去执行。(这次的使用也算是一个练习,踩到了@Async的坑,整体还是有收获的)
  2. 后面又加上的@Transactional注解,在有些文章上看到是和@Async注解存在冲突的,会使事务的回滚失效,但是在测试的时候并没有发现事务回滚失效,出现异常数据库能够正常回滚,后续还需要再去验证。

新知识

@Async注解失效场景

  1. 没有使用@EnableAsync注解
  2. 内部方法调用,没有走Spring的代理
  3. public方法
  4. 相关方法的返回值必须是void或者Future,否则失效
  5. 方法用static或者final修饰
  6. 业务类没有被Spring管理,例如没加@Service之类的注解,自己new的对象
  7. 使用@Transactional@Async 同时注解方法,导致事务失效
  8. 线程池配置有误
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值