背景
系统有一个接口,业务比较复杂,执行耗时会很长时间,但是前端页面需要很快返回结果。
使用@Async注解,将执行耗时很长的逻辑异步处理。
不过这个异步处理逻辑里面又需要依次去执行一系列执行耗时很长的逻辑。 最后需要对这些耗时较长任务的执行结果做一些处理。
伪代码:
@Async("xxxx")
public void xxxx(){
//做一些增删改查
doSomethings()
for(xxx xx : xxx{
//这个操作很耗时间,且for次数比较多,如果依次执行,会很耗时间,所以就想将里面的任务也并行。
xx.doSomeThing();
}
//需要根据for循环中每一个任务的执行结果去做一些操作
doSomeThings();
}
所以
- 第一层异步:将整个耗时较长的逻辑写到一个 @Async注解下的方法里
- 第二层异步:将里面需要依次执行的任务也扔到线程池里面去,让他们同时执行(需要保证线程池核心大小 > 需要执行任务的次数
- 因为第一层异步还需要用到第二层异步的一些结果,所以利用CountDownLatch来做 线程协作
代码
- 线程池配置
@Configuration
@EnableAsync
public class ExecutorConfig {
@Bean("taskExecutor")
public Executor taskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(20);
taskExecutor.setMaxPoolSize(50);
taskExecutor.setQueueCapacity(200);
taskExecutor.setKeepAliveSeconds(60);
taskExecutor.setThreadNamePrefix("taskExecutor--test-");
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(60);
return taskExecutor;
}
}
- 主方法
@Component
public class CountDownLatchTestJob {
private final static Logger log = LoggerFactory.getLogger(CountDownLatchTestJob.class);
@Autowired
private InsideJob insideJob;
@Async("taskExecutor")
public void mainMethod() throws InterruptedException {
long begin = System.currentTimeMillis();
log.info("start main method");
log.info("main method thread: {}", Thread.currentThread().getName());
CountDownLatch count = new CountDownLatch(10);
for(int i = 0; i < 10; i++){
insideJob.insideCdlMethod(count);
}
count.await();
log.info("main method end");
long end = System.currentTimeMillis();
log.info("task cost time:{}", end - begin);
}
}
- 内部方法
@Component
public class InsideJob {
private final static Logger log = LoggerFactory.getLogger(InsideJob.class);
@Async("taskExecutor")
public void insideCdlMethod(CountDownLatch count){
log.info("start inside method " + count.getCount());
try {
Thread.sleep(1000);
log.info("inside method thread: {}", Thread.currentThread().getName());
} catch (InterruptedException e) {
log.warn(String.format("xxxxx %s", "sdw"), e);
}
count.countDown();
log.info("inside method end " + count.getCount());
}
}
主方法将CountDownLatch作为参数传入内部异步方法,然后主方法一直等着内部异步方法执行完成。
才能继续下面的处理逻辑。
提示
这里的主异步方法和内部异步方法不能写在一个类里面,不然mainMethod 调用 insideCdlMethod的时候,会因为没有生成代理,而没有办法启用新的线程。