本文主要是介绍多线程并发调用feign,outcome返回NullPointerException,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
遇到的问题
看过我之前的文章能发现我从小就有一个并发梦,并且也是对线程协程纤程异步以及并发包有一些了解。这次的接口正好需要多次查询,并发调用feign请求提上日程。
List<Future<CurveLineDataVO>> futureList = Lists.newArrayList();
for (CurveDTO curveDTO : curveDTOList) {RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);Callable<CurveLineDataVO> callable = () -> getIntervalFlowCurve(curveDTO);futureList.add(pool.submit(callable));
}
for (Future<CurveLineDataVO> future : futureList) {try {curveLineDataVOS.add(future.get());} catch (InterruptedException e) {log.error("future.get()失败", e);} catch (ExecutionException e) {throw new RuntimeException(e.getCause().getMessage());}
}
可是,future.get一直抛出NullPointerException。
调试发现返回的FutTask对象的
state = 3;
outcome = NullPointerException
查找资料得知原因是HttpServletRequest 为null
Spring Boot 默认使用ThreadLocal把Request设置进请求线程中,这样如果在请求方法里面另起一个子线程然后再通过getRequestAttributes方法获取,是获取不到的。
。。。。。。。。。。。。。。。。。。
在Spring cloud微服务中,feign开启了熔断器(hystrix):feign.hystrix.enabled=ture,并且使用默认的信号隔离级别,、HttpServletRequest对象在父线程与子线程是相互独立的,不共享的。所以子线程中使用父线程的HttpServletRequest数据为null。
如上,加一句问题确实得到了解决。
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
但是并没有完全解决:用线程池时,新创建的线程能传入token,线程已经存在被再次利用时还是拿不到。这时需要结合threadLocal,Feign的拦截器,来解决问题。
三次请求:for循环与线程池加callable的效率差距有一倍。
线程池ThreadPoolExecutor参数设置
解决了调用不通的问题后,就该解决如何设置线程池来提高资源利用性价比。
corePoolSize、maxPoolSize、queueCapacity。
核心线程数、最大线程数、队列容量对线程池是否继续创建线程的影响关系如下:
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
1. 若线程数小于最大线程数,创建线程
2. 若线程数等于最大线程数,抛出异常,拒绝任务
这方面还需要继续学习。以下是这次实践的一些体会。
阿里java开发规范说不要用Executors创建线程池。
Executors.newFixedThreadPool()
由于这个接口调用才会用到线程池,所以将corePoolSize设置为大于0,没任务也会存活,难免觉得浪费资源。
但是当corePoolSize=0时,其他参数怎么设置,效率都上不来,例如:
private static final ExecutorService pool = new ThreadPoolExecutor(
0, 20, 5L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
原因是
他会将任务加入队列,然后创建一个线程执行,队列不满不会创建更多的线程去执行。导致线程池只有一个线程活跃。
如图,只有thread1.
于是,我想着,若是队列满了会怎样呢,把队列的容量设置为1
private static final ExecutorService pool = new ThreadPoolExecutor(
0, 20, 5L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1));
执行线程确实多了,效率提升明显。
但是这样的设置也有一个致命的缺陷。
就是当maxPoolSize太小了,任务数量太多,就会被拒绝而报错。
java.util.concurrent.RejectedExecutionException
Task java.util.concurrent.FutureTask@53b8ecad rejected from java.util.concurrent.ThreadPoolExecutor@1ad994af
allowCoreThreadTimeout
还有一个allowCoreThreadTimeout参数,或许能达到我想让线程池不断给我创建新的线程去执行,同时在空闲时也不占用资源。
allowCoreThreadTimeout = true
能让核心线程在超时后退出,直到核心线程数量=0
最后决定
private static final ThreadPoolExecutor pool;static {pool = new ThreadPoolExecutor(3, 5, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());pool.allowCoreThreadTimeOut(true);
}
这篇关于多线程并发调用feign,outcome返回NullPointerException的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!