SpringBoot @Async + Completable实现异步踩到的坑

476 阅读2分钟

最近在项目中使用SpringBoot @Async注解 + Java8 Completable实现异步时候不小心踩坑了,所以写下这篇文章总结一下。

一、配置类

第一步:新建一个配置类,实现AsyncConfigurer接口;
第二步:实现getAsyncExecutor方法,该方法返回一个线程池对象;
第三步:实现getAsyncUncaughtExceptionHandler方法,该方法返回一个SimpleAsyncUncaughtExceptionHandler对象,该对象用于简单地记录异常信息;
第四步:在配置类上使用@EnableAsync注解;

@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    @Bean(name = "taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("order-Executor-");
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

二、实现异步方法

第一步:新建一个组件类,可以是Spring管理的任何组件,比如service、component等;
第二步:在类中提供异步方法,在方法上使用@Async注解;

@Service
public class DemoService {

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask1() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test1");
    }

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask2() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test2");
    }

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask3() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test3");
    }
 }

上面每一个异步方法需要执行3秒。

三、调用异步方法

这里必须要在另外一个类上调用上面定义的异步方法。

@SpringBootApplication
@Controller
public class AsynctestApplication {

    @Autowired
    DemoService demoService;

    public static void main(String[] args) {
        SpringApplication.run(AsynctestApplication.class, args);
    }

    @RequestMapping("/index")
    @ResponseBody
    public String index() {
        long start = System.currentTimeMillis();
        CompletableFuture<String> task1 = demoService.asyncTask1();
        CompletableFuture<String> task2 = demoService.asyncTask2();
        CompletableFuture<String> task3 = demoService.asyncTask3();
        CompletableFuture[] collect = {task1, task2, task3};
        CompletableFuture<List<String>> res =  CompletableFuture.allOf(collect).thenApply(ignoredVoid -> {
            List list = Arrays.stream(collect).map(CompletableFuture::join).collect(Collectors.toList());
            System.out.println(list);
            return list;
        });
        try {
            // 控制异步任务的执行时间
            res.get(5, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("一共花费了" + (end - start) + "毫秒");
        return "index";
    }
 }

从运行结果来看,因为三个方法是异步同时执行,所以程序运行了3秒多就结束了。 在这里插入图片描述 最后说一下我踩到的坑,就是开始时候将调用异步代码的方法也放在异步方法所在类中实现,导致异步方法执行失效。比如下面的query方法:

@Component
public class DemoService {

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask1() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test1");
    }

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask2() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test2");
    }

    @Async("taskExecutor")
    public CompletableFuture<String> asyncTask3() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("test3");
    }

    public List<String> query() {
        CompletableFuture<String> task1 = asyncTask1();
        CompletableFuture<String> task2 = asyncTask2();
        CompletableFuture<String> task3 = asyncTask3();
        CompletableFuture[] collect = {task1, task2, task3};
        CompletableFuture<List<String>> res =  CompletableFuture.allOf(collect).thenApply(ignoredVoid -> {
            List list = Arrays.stream(collect).map(CompletableFuture::join).collect(Collectors.toList());
            return list;
        });
        try {
            return res.get(5, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return null;
    }

}

因为query方法与其他异步方法放在同一个类中,导致程序异步功能失效。这时候即使任务超时了,也不会有抛出java.util.concurrent.TimeoutException异常。 在这里插入图片描述