当前位置:首页 > 文章列表 > 科技周边 > 业界新闻 > Java 25 虚拟线程文档更新后如何重新评估阻塞式服务

Java 25 虚拟线程文档更新后如何重新评估阻塞式服务

来源:17golang原创 2026-09-14 21:54:42 0浏览 收藏

我最近重新看 Java 25 的虚拟线程文档时,最容易误判的一点不是 API,而是“线程池大小”这个旧指标。虚拟线程适合把大量等待网络、数据库或文件的任务写成直线代码,但它不会让一次 SQL 或一次 HTTP 调用本身变快。重新评估阻塞式服务时,应该把承载任务的线程数和保护下游资源的并发额度拆开。

如果服务的大部分时间都在等待,并且并发任务数较高,可以先把请求执行器改成每任务一个虚拟线程;数据库连接数、下游限额和 CPU 计算量仍要单独限制。迁移是否成功,要看吞吐、排队和资源利用率是否改善,而不是只看线程数量。
要点速览
  • 虚拟线程优化的是高并发等待型负载的吞吐,不是单请求延迟。
  • 不要用虚拟线程池限制数据库或下游服务,应该用信号量、连接池和明确的配额。
  • Java 25 的 JFR、线程转储和 VirtualThreadSchedulerMXBean 可以帮助确认迁移后的真实瓶颈。

先判断:阻塞是不是服务的主要工作

传统固定线程池把“平台线程很贵”和“下游最多允许多少并发”混在了一个数字里。例如线程池设为 200,可能只是因为机器无法长期承载更多平台线程,也可能是数据库连接池只有 200 个连接。这两个限制并不是一回事。

可以先按一次请求的时间拆分:CPU 执行占比、等待 HTTP 或 JDBC 的时间、排队等待连接的时间,以及序列化和日志时间。如果 CPU 已经接近核心数,虚拟线程不会凭空增加计算能力;如果请求大部分时间停在可阻塞的 I/O 上,增加可挂起的任务数才有意义。JEP 444 对这一点的表述很明确:虚拟线程追求的是 scale,而不是 speed。

观察到的现象优先处理方式不要误判为
大量请求等待网络,平台线程长期空闲或排队尝试每任务一个虚拟线程单次网络调用会变快
CPU 持续满载,任务主要做压缩或计算保持 CPU 并行度接近核心数虚拟线程能替代计算线程池
连接池耗尽、下游返回限流单独设置连接数和信号量把虚拟线程数量改小就解决了资源边界

把任务执行器改成每任务一个虚拟线程

Java 25 API 文档中的 Executors.newVirtualThreadPerTaskExecutor() 会为每个提交的任务创建一个虚拟线程,它不是把虚拟线程放进传统意义上的共享线程池。对请求、批量小任务或扇出调用来说,最小改法是保留 ExecutorService 的提交和等待语义,只替换执行器的来源。

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 每个外部调用对应一个独立任务,不复用昂贵的平台线程
    var user = executor.submit(() -> userClient.fetch(userId));
    var orders = executor.submit(() -> orderClient.fetch(userId));

    // 等待两个结果,异常沿着任务边界返回给请求处理代码
    return new Profile(user.get(), orders.get());
} catch (InterruptedException e) {
    // 保留中断标记,避免上层取消信号被吞掉
    Thread.currentThread().interrupt();
    throw new ServiceUnavailableException(e);
} catch (ExecutionException e) {
    // 生产代码应按 cause 分类记录下游错误
    throw new DownstreamException(e.getCause());
}

这里的关键不是把并发数调到很大,而是让一个请求在等待时释放 carrier。执行器用 try-with-resources 关闭时会等待已经提交的任务,因此特别适合一次请求内的有限扇出。任务本身仍要有超时、取消和异常分类,虚拟线程不会替你补上这些业务边界。

Java 25 虚拟线程每任务执行器与阻塞式 HTTP 和 JDBC 调用的操作输入示意图
图1:Java 25 虚拟线程每任务执行器的操作示意图;请求任务、等待型调用和执行边界均为原创解释性绘制,不是真实运行截图。

别再用线程池大小限制数据库和下游服务

迁移后最常见的回退是继续创建一个“虚拟线程池大小为 50”的包装器。这样既失去了大量挂起任务的伸缩空间,也没有表达真正的资源约束。更清楚的写法是:虚拟线程负责承载请求,信号量负责保护一个最多允许 50 个并发操作的下游。

private final Semaphore downstreamSlots = new Semaphore(50);

Result callDownstream(Request request) throws Exception {
    downstreamSlots.acquire(); // 只限制下游额度,不限制虚拟线程总数
    try {
        return httpClient.send(request); // 阻塞等待时,虚拟线程可以让出 carrier
    } finally {
        downstreamSlots.release(); // 无论成功或失败都归还额度
    }
}

数据库连接池、HTTP 客户端连接上限、消息系统配额和供应商限流都应有自己的指标。信号量也不是万能的:如果任务拿到许可后又做很长的 CPU 计算,应该缩小受保护区间;如果下游库已经内置连接池,则要确认外层信号量不会造成双重排队。

重新检查三个容易被忽略的边界

第一是 ThreadLocal。虚拟线程支持线程本地变量,但每个任务一个线程会改变“在线程池线程上缓存昂贵资源”的经济性。连接、会话或大对象不应因为换成虚拟线程而被每个请求各自复制,应该交给连接池、请求上下文或显式缓存。

第二是长时间阻塞的临界区和 native 调用。虚拟线程在某些场景下无法从 carrier 卸载;频繁且长时间的 pinning 会降低并发收益。JFR 的虚拟线程事件和 jdk.tracePinnedThreads 可以用来定位,而不是凭感觉把所有 synchronized 全部替换。

第三是观测方式。Java 25 提供的 VirtualThreadSchedulerMXBean 可以查看调度器目标并行度、carrier 数量和排队的虚拟线程数。迁移前后至少对比下表中的指标:

指标要回答的问题异常信号
请求吞吐与 p95/p99等待型并发是否真正转化为更多完成请求吞吐不变但尾延迟升高
数据库连接池等待瓶颈是否已经转移到数据库线程变多,连接等待更长
虚拟线程队列与 carrier 数调度器是否积压任务队列持续增长或 CPU 饱和
JFR pinned 事件是否有同步块或 native 调用卡住 carrier长时间、频繁出现 pinning
Java 25 虚拟线程调度器 JMX 与 JFR 观测指标的结果示意图
图2:用调度器队列、carrier 数量和 JFR pinned 事件复查迁移结果的示意图;面板数据为解释概念而绘制。

常见问题

虚拟线程能替代所有固定线程池吗?

不能。它适合承载大量等待型任务;CPU 密集型工作、定时调度、批量资源配额和有界队列仍需要明确的并行度或背压设计。

迁移后还需要连接池吗?

需要。虚拟线程不是数据库连接,也不会增加数据库能处理的并发量。连接池大小应按数据库和业务压测结果设置。

看到 synchronized 就必须改成 ReentrantLock 吗?

不必。只保护短小内存操作或只在启动阶段执行的同步块通常不值得改;优先调查会在同步区内进行长时间等待的热点。

怎样判断这次迁移值得保留?

用同一流量、同一依赖配额比较吞吐、尾延迟、CPU、连接池等待、队列长度和错误率。如果只是线程数下降而用户结果没有改善,就还没有证明迁移有效。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go zip.FileHeader.Method 设置错误会导致压缩包打不开吗Go zip.FileHeader.Method 设置错误会导致压缩包打不开吗
上一篇
Go zip.FileHeader.Method 设置错误会导致压缩包打不开吗
Go os/signal 通道缓冲太小会漏掉哪些信号
下一篇
Go os/signal 通道缓冲太小会漏掉哪些信号
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    26次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    130次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    60次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    22次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    81次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码