一场由CompletableFuture和ExecutorService引发的血案
现象
程序运行过程中无缘无故卡住,方法执行过程中停滞不前
解决
-
根据前段请求找出哪个方法卡住了,发现了方法m
-
本地debug发现每次进到m方法里面就卡住
-
不给CompletableFuture传入ExecutorService参数(默认是ForkJoinPool)就不会卡死,一度严重怀疑是我们ExecutorService配置出了问题
-
m方法里面大量使用了
CompletableFuture
以及注入的线程池 -
怀疑是线程耗尽,但是我们的队列配置得比较大,卡住的时候观察了队列,还剩下很多位置,原则上会执行很慢,但是不会卡住
-
于是将线程池数量从16改为200,果然成功执行,那么说明确实是线程耗尽
-
配置spring线程池:
spring.task.execution.pool.allow-core-thread-timeout=true
,意思是:core线程在闲时也会销毁 -
发现本地即使没有请求,线程也一直不销毁,基本上就是死锁了
-
分析业务代码,类似如下(类似下面代码):
@Test void test() throws Exception { // 使用此就会卡住 ExecutorService pool = Executors.newFixedThreadPool(1); // 使用此就不会 ExecutorService pool = ForkJoinPool.commonPool(); CompletableFuture
resp = CompletableFuture.supplyAsync(() -> { CompletableFuture result = CompletableFuture.supplyAsync(() -> { try { SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return "dd"; }, pool); try { result.get(); } catch (Exception e) { e.printStackTrace(); } return "ok"; }, pool); resp.get(); } -
外层全部占用线程池的线程,而里面有需要等待内层的CompletableFuture返回结果,而内层又需要等待外层释放线程
结果
- 死锁造成的,ExecutorService配置的足够大就不会出错,但是这不治根
- 为什么forkjoinpool就不会出错呢?forkjoinpool会源源不断的创建线程
解决方案
不要嵌套使用CompletableFuture