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

虚拟线程专属死锁问题求助:仅50线程场景下触发

虚拟线程Executor死锁问题排查与解决思路

问题场景

使用虚拟线程池时,当CONCURRENT_WORKERS设为50,主线程调用completionService.take()会无限等待;设为20或切换为平台线程池(Executors.newFixedThreadPool(CONCURRENT_WORKERS))时无此问题。核心代码差异仅在于线程工厂:

  • 平台线程池:
private static final ExecutorService taskExecutor = Executors.newFixedThreadPool(CONCURRENT_WORKERS);
  • 虚拟线程池:
private static final ExecutorService taskExecutor = Executors.newFixedThreadPool(CONCURRENT_WORKERS, Thread.ofVirtual().factory());

线程池被包装为CompletionService,主线程批量提交任务后循环调用take()获取结果。

核心排查方向

结合httpclient-4.5.14的疑似关联点,重点围绕虚拟线程固定(Pinned Threads)和资源瓶颈展开:

1. 确认虚拟线程是否被固定

虚拟线程进入synchronized块/方法、调用本地方法时会被固定到平台线程,若大量虚拟线程被固定且占用所有可用平台线程,会导致任务调度停滞。

  • 生成JSON格式线程dump(能完整展示虚拟线程状态):
    jcmd <进程PID> Thread.dump_to_file -format=json thread_dump.json
    
    查看每个虚拟线程的state和blockedOn字段,确认是否卡在httpclient的synchronized方法(如PoolingHttpClientConnectionManager的borrowConnection)。
  • 强化固定线程追踪:
    添加-Djdk.virtualThreadScheduler.maxPoolSize=20(增大平台线程池上限),同时保留-Djdk.tracePinnedThreads=full,运行后查看日志是否有固定线程的输出。

2. 排查httpclient连接池瓶颈

httpclient-4.5.x默认连接池配置为:全局最大连接数20,单路由最大连接数2。当50个虚拟线程同时发起请求时,会有大量线程阻塞等待连接,而这些阻塞的虚拟线程因处于synchronized块被固定到平台线程,最终耗尽平台线程资源,导致所有任务无法推进,主线程拿不到结果。

  • 临时增大连接池配置验证:
    PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
    connManager.setMaxTotal(100); // 全局最大连接数
    connManager.setDefaultMaxPerRoute(50); // 单路由最大连接数
    CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).build();
    

3. 检查任务执行链路的阻塞点

  • 确认doTask方法除了http请求外,是否还有其他synchronized或阻塞逻辑(如数据库锁、文件IO),这些点也可能导致虚拟线程固定。
  • 验证storeData方法是否存在同步阻塞,若该方法持有锁且执行缓慢,会导致已完成任务的结果无法被及时处理,间接影响任务调度。

具体解决建议

1. 优化httpclient配置或版本

  • 调整连接池参数:根据并发需求增大maxTotal和defaultMaxPerRoute,减少线程等待连接的时间,降低虚拟线程被固定的概率。
  • 升级到HttpClient 5.x:该版本用非阻塞锁(如ReentrantLock)替代了部分synchronized代码,大幅减少虚拟线程固定的场景。

2. 调整虚拟线程调度器参数

通过JVM参数增大虚拟线程调度器的平台线程数,避免被固定的虚拟线程耗尽资源:

-Djdk.virtualThreadScheduler.parallelism=16 # 根据CPU核心数调整,如8核设为16
-Djdk.virtualThreadScheduler.maxPoolSize=30 # 平台线程池最大上限

3. 隔离阻塞性任务

将http请求这类会导致虚拟线程固定的阻塞逻辑,提交到单独的平台线程池执行,虚拟线程仅处理非阻塞业务:

// 单独创建平台线程池处理http请求
private static final ExecutorService httpExecutor = Executors.newFixedThreadPool(20);

// doTask方法中调整为异步调用http请求
private Data doTask() throws Exception {
    CompletableFuture<HttpResponse> httpFuture = CompletableFuture.supplyAsync(() -> {
        // 执行http请求逻辑
        return httpClient.execute(...);
    }, httpExecutor);
    HttpResponse response = httpFuture.join();
    // 解析response并生成Data
    return parseData(response);
}

4. 验证任务提交完整性

确认TASK_COUNT与实际提交的任务数一致,检查doTask是否会抛出未捕获异常(虽然CompletionService会接收异常的Future,但需确保所有任务都被成功提交到线程池)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:50:02