CountDownLatch计数未递减排查:嵌套ExecutorService场景
核心问题点
1. 自旋循环阻塞外层线程,后续代码无法执行
GrpcApiRunnable.run()中的while(System.currentTimeMillis()<serviceEndTime)是无限制的自旋循环,会持续占用外层线程池的线程,疯狂向内部线程池提交任务,直到duration时间耗尽才会执行后续的awaitTerminationAfterShutdown和countDownLatch.countDown()。如果duration设置较长,这个循环会直接卡住当前线程,导致CountDownLatch的计数迟迟无法递减。
同时,这种无控制的任务提交会瞬间填满内部线程池的任务队列,引发RejectedExecutionException,一旦异常未被捕获,会直接终止run()方法,countDownLatch.countDown()根本不会执行。
2. 未处理异常,countDown逻辑被跳过
awaitTerminationAfterShutdown方法通常包含executorService.shutdown()和executorService.awaitTermination(...),而awaitTermination会抛出InterruptedException。如果没有对该异常做捕获处理,一旦线程被中断,异常会向上抛出,直接终止run()方法,导致countDownLatch.countDown()无法执行,这是CountDownLatch计数未递减的最常见原因。
3. 任务提交逻辑不合理
自旋提交任务的方式完全没有控制速率,不仅会压垮内部线程池,还会浪费外层线程的资源。
修复方案
1. 重构任务提交逻辑,避免自旋阻塞
用单独线程负责提交任务,释放外层线程的同时控制提交速率,避免任务队列爆炸:
@Override public void run() { ExecutorService executorService = Executors.newFixedThreadPool(noOfThreads); long serviceEndTime = System.currentTimeMillis() + duration; // 单独线程处理任务提交,不阻塞外层线程 Thread submitThread = new Thread(() -> { while (System.currentTimeMillis() < serviceEndTime) { try { executorService.submit(() -> { // do something }); // 控制提交速率,按需调整间隔 Thread.sleep(10); } catch (RejectedExecutionException e) { // 处理线程池拒绝异常 e.printStackTrace(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } executorService.shutdown(); // 提交完成后关闭线程池 }); submitThread.start(); try { // 等待内部线程池所有任务执行完成 if (!executorService.awaitTermination(duration + 1000, TimeUnit.MILLISECONDS)) { executorService.shutdownNow(); // 超时强制关闭 } submitThread.join(); // 等待提交线程结束 } catch (InterruptedException e) { executorService.shutdownNow(); submitThread.interrupt(); Thread.currentThread().interrupt(); } finally { // 无论是否异常,确保countDown执行 countDownLatch.countDown(); } }
2. 外层线程池使用后关闭
在performanceTestV21方法中,提交完所有任务后关闭外层线程池,避免资源泄漏:
public JsonObject performanceTestV21(long duration, int noOfThreads) throws InterruptedException { int testCount = getGrpcTestInfos().size(); ExecutorService outerExecutorService = Executors.newFixedThreadPool(testCount); CountDownLatch countDownLatch = new CountDownLatch(testCount); for (GrpcTestInfo grpcTestInfo : getGrpcTestInfos()) { outerExecutorService.submit(new GrpcApiRunnable(noOfThreads, duration, countDownLatch)); } outerExecutorService.shutdown(); // 关闭外层线程池,不再接受新任务 countDownLatch.await(); // 可选:等待外层线程池剩余任务完成 if (!outerExecutorService.awaitTermination(duration + 2000, TimeUnit.MILLISECONDS)) { outerExecutorService.shutdownNow(); } return new JsonObject(performanceMatrix); }
内容的提问来源于stack exchange,提问作者Arindam

