问题描述:
CompletableFuture广泛应用于快速构建异步线程调用,在使用过程中,不当地使用可能会导致属性注入的service或者Mapper持久层,ClassLoader未能正常获取类,导致ClassNotFindException,该问题的发现也是在部分代码的书写中,有部分地方忘记配置线程池,从而导致了问题的出现。
public void doTask(Request request){
AService bean = SpringUtils.getBean(AService.class);
CompletableFuture.runAsync(() -> {
bean.doSomething();
DbEntity dbSubtask = dbEntityMapper.selectOne(Wrappers.lambdaQuery(DbEntity.class)
.eq(DbEntity ::getId, request.getId())
.eq(DbEntity ::getName, request.getName()));
return null;
}).exceptionally(throwable -> {
Optional.ofNullable(throwable).ifPresent(item -> {
log.error("translateEpisode 发生错误:", item);
});
});
}
临时解决方案及思路:
CompletableFuture在没有指定工作线程池的情况下,会使用默认提供的forkJoin线程池,在Tomcat容器中,tomcat没有直接使用jdk8原生的ForkjoinPool
,而是定制了自己的SafeForkJoinWorkerThread
且将从线程的contextClassLoader设置为null导致的,以此来解决java8的默认的ForkJoinPool内存泄漏问题。如果在ForkJoinPool里面使用LambdaWrapper出现ClassNotFoundException就会是个必现问题;
我当时遇到报错信息是在一个service方法中,通过在CompletableFuture外getBean的方式,临时解决了这个问题;
最终解决思路:
通过查阅相关的issue,通过指定worker线程池,可以避免这一情况的发生,也可以通过这种方式进行处理;
线程池的示例代码:
@Bean(name = "workerExecutor")
public Executor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
这个问题其实也可以作为,为什么在使用CompletableFuture时建议指定执行线程池的原因之一;
参考issue:
https://cloud.tencent.com/developer/article/1511111