You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Project Loom时ExecutionCompletionService挂起问题排查求助

问题描述

尝试使用Project Loom实现REST服务器的并行调用,需处理约50000次API请求,按500条为一批进行处理。如下代码片段中,使用虚拟线程时ExecutorService会出现挂起:通过take()方法获取约490+个响应后就停滞,但使用传统系统线程无此问题,且批次规模较小时也能正常运行。

public List<ApiResponseWrapper> sendRequestToApi(List<ApiRequest> apiRequests) throws ExecutionException, InterruptedException {
    ThreadFactory factory = Thread.ofVirtual().factory();
    List<ApiResponseWrapper> apiResponseWrappers = new ArrayList<>();
    final ExecutorService pool = Executors.newFixedThreadPool(7, factory);
    final CompletionService<ApiEntityResponseWrapper> service = new ExecutorCompletionService<>(pool);
    List<RestTemplateClient> webClientCallables = apiRequests.stream().map(ApiRequest -> new RestTemplateClient(ApiRequest,restTemplate)).toList();
    webClientCallables.forEach(service::submit);
    pool.shutdown();
    int i = 0;
    try{
        for(RestTemplateClient webClient : webClientCallables){
            ApiResponseWrapper apiEntityResponseWrapper = service.take().get();
            apiResponseWrappers.add(apiEntityResponseWrapper);
            log.info("The size in webclient callables are reduced by {}", webClientCallables.size()-i);
            i++;
        }
    }
    catch (InterruptedException | ExecutionException | TibcoClientException ee){
        log.error("An exception occurred while processing requests towards Tibco ", ee.getCause());
        throw ee;
    }
    return apiResponseWrappers;
}
问题分析与原因

你的代码出现挂起的核心原因集中在虚拟线程特性与RestTemplate底层资源的交互冲突,具体拆解如下:

1. 底层连接池资源耗尽导致任务阻塞

RestTemplate默认依赖HttpURLConnection,其内置连接池对单目标主机的并发连接数有默认限制(通常为5-20)。你用FixedThreadPool(7)创建虚拟线程池,虽然线程数固定为7,但虚拟线程的调度机制会在IO阻塞时让载体线程切换处理其他任务,看似能并行推进更多请求,实则底层连接池的并发上限并未改变。当处理500个请求时,大量任务会排队等待空闲连接,部分任务因连接池耗尽陷入无限阻塞,导致CompletionService.take()一直等待未完成的任务。

而系统线程池场景下,7个系统线程同时发起请求,连接池默认配置刚好能支撑,任务完成后会及时释放连接,因此不会挂起;小批次请求未触及连接池上限,自然也能正常运行。

2. 固定虚拟线程池的不合理性

虚拟线程的设计初衷是为每个阻塞IO任务创建独立虚拟线程,利用其轻量特性承载高并发。你用FixedThreadPool(7)限制虚拟线程数量,完全浪费了虚拟线程的优势,反而让任务排队逻辑和系统线程池无异。且虚拟线程的调度机制在固定池模式下,可能出现任务调度优先级异常,导致部分任务无法被及时唤醒处理。

3. 潜在的ThreadLocal状态冲突

如果RestTemplate或其底层客户端依赖ThreadLocal存储连接状态,虚拟线程复用载体线程时,可能出现ThreadLocal状态泄露或冲突。比如某个连接被错误绑定到多个虚拟线程,导致连接无法正常释放,最终耗尽连接池资源。

解决方案建议
  • 替换ExecutorService类型:使用Executors.newVirtualThreadPerTaskExecutor()替代固定大小的虚拟线程池,让每个请求对应一个虚拟线程,充分发挥虚拟线程的并发优势。
  • 自定义连接池配置:切换到Apache HttpClient或OkHttp作为RestTemplate的底层客户端,显式配置足够的最大连接数、连接超时和读取超时,避免因连接耗尽导致任务阻塞。
  • 添加全局异常捕获:在RestTemplateClient的call()方法中添加全局异常捕获,确保所有异常都被封装到Future中,避免任务因未捕获异常陷入未知状态。
  • 调整批次处理逻辑:可适当降低单批次请求量,或配合连接池的动态扩容策略,平衡并发量与资源占用。

内容的提问来源于stack exchange,提问作者Hari R

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 17:07:20