<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>高并发 on 文艺技术笔记</title>
        <link>https://wenyiblog.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/</link>
        <description>Recent content in 高并发 on 文艺技术笔记</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>文艺技术笔记 | 软件工程师文艺</copyright>
        <lastBuildDate>Wed, 19 Aug 2026 14:10:00 +0800</lastBuildDate><atom:link href="https://wenyiblog.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Java 23虚拟线程在高并发场景下的性能边界测试：从HTTP服务到消息消费的性能对比</title>
        <link>https://wenyiblog.top/2026/08/blog_java23_vthread_2/</link>
        <pubDate>Wed, 19 Aug 2026 14:10:00 +0800</pubDate>
        
        <guid>https://wenyiblog.top/2026/08/blog_java23_vthread_2/</guid>
        <description>&lt;h1 id=&#34;java-23虚拟线程在高并发场景下的性能边界测试从http服务到消息消费的性能对比&#34;&gt;&lt;a href=&#34;#java-23%e8%99%9a%e6%8b%9f%e7%ba%bf%e7%a8%8b%e5%9c%a8%e9%ab%98%e5%b9%b6%e5%8f%91%e5%9c%ba%e6%99%af%e4%b8%8b%e7%9a%84%e6%80%a7%e8%83%bd%e8%be%b9%e7%95%8c%e6%b5%8b%e8%af%95%e4%bb%8ehttp%e6%9c%8d%e5%8a%a1%e5%88%b0%e6%b6%88%e6%81%af%e6%b6%88%e8%b4%b9%e7%9a%84%e6%80%a7%e8%83%bd%e5%af%b9%e6%af%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Java 23虚拟线程在高并发场景下的性能边界测试：从HTTP服务到消息消费的性能对比
&lt;/h1&gt;&lt;p&gt;虚拟线程自 JDK 21 正式落地以来，被广泛认为是&amp;quot;以极低成本吃掉高并发 IO&amp;quot;的终极方案。但在真实生产里，它的表现远没有宣传那么&amp;quot;无脑快&amp;quot;——同样一段代码，放在 HTTP 服务里可能吞吐翻几倍，放进 Kafka 消费端却可能毫无提升甚至踩进 pinning 的坑。&lt;/p&gt;
&lt;p&gt;这篇文章不是概念科普，而是一组我们在 JDK 23 上跑出来的压测实录：两个典型场景（HTTP 短请求服务、Kafka 消息消费）、三种线程模型（固定平台线程池、可调平台线程池、虚拟线程），全程记录吞吐、延迟与内存占用。文末附上踩坑清单和参数调优建议，希望给正在做技术选型的你一个可落地的参考。&lt;/p&gt;
&lt;h2 id=&#34;一为什么要重新测一遍&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e9%87%8d%e6%96%b0%e6%b5%8b%e4%b8%80%e9%81%8d&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、为什么要重新测一遍
&lt;/h2&gt;&lt;p&gt;先说结论背后的动机。团队内部此前一直用固定线程池 + 队列削峰的老套路，随着业务量上涨，单机线程数逼近两千，线程上下文切换和内存占用都开始吃紧。某企业负责人拍板引入虚拟线程做技术改造，但架构师提出一个很实际的问题：&lt;strong&gt;虚拟线程的收益到底在哪些场景成立？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有句话说得好：&amp;ldquo;没有边界的优化都是耍流氓。&amp;ldquo;虚拟线程不是银弹，它的优势边界必须用数据划出来，否则上线后才发现瓶颈不在线程而在别处，成本就高了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是我们设计了这组对比实验，目标就三个：量化收益、划清边界、给出可复用的调优参数。&lt;/p&gt;
&lt;h2 id=&#34;二压测环境与配置&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e5%8e%8b%e6%b5%8b%e7%8e%af%e5%a2%83%e4%b8%8e%e9%85%8d%e7%bd%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、压测环境与配置
&lt;/h2&gt;&lt;p&gt;测试在两套同样的规格上各跑一轮，避免硬件差异干扰结论。应用节点部署 JDK 23 的 OpenJDK 构建，Kafka 使用 3.7 版本，消息为 1KB 的 JSON 记录。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;配置项&lt;/th&gt;
					&lt;th&gt;应用节点 ×2&lt;/th&gt;
					&lt;th&gt;Kafka 节点 ×2&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;CPU&lt;/td&gt;
					&lt;td&gt;8 核（Intel Xeon 8375C）&lt;/td&gt;
					&lt;td&gt;8 核（Intel Xeon 8375C）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内存&lt;/td&gt;
					&lt;td&gt;16GB&lt;/td&gt;
					&lt;td&gt;16GB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;JDK / 组件&lt;/td&gt;
					&lt;td&gt;OpenJDK 23，G1 收集器&lt;/td&gt;
					&lt;td&gt;Kafka 3.7，3 分区 ×2 副本&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;压测工具&lt;/td&gt;
					&lt;td&gt;wrk / 自研 Kafka 消费打点&lt;/td&gt;
					&lt;td&gt;kafka-producer-perf-test&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;指标口径&lt;/td&gt;
					&lt;td&gt;吞吐、P50/P99 延迟、常驻内存&lt;/td&gt;
					&lt;td&gt;消费速率、堆积水位、内存&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;压测前统一做一轮预热（HTTP 5 分钟、消息消费 10 分钟），指标取稳定期的均值。需要说明的是，以下数据是在该硬件与参数组合下的实测值，换机器、换 JDK 版本会有波动，但&lt;strong&gt;相对趋势具有普适性&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;三场景一http-短请求服务&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e5%9c%ba%e6%99%af%e4%b8%80http-%e7%9f%ad%e8%af%b7%e6%b1%82%e6%9c%8d%e5%8a%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、场景一：HTTP 短请求服务
&lt;/h2&gt;&lt;h3 id=&#34;31-测试设计&#34;&gt;&lt;a href=&#34;#31-%e6%b5%8b%e8%af%95%e8%ae%be%e8%ae%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.1 测试设计
&lt;/h3&gt;&lt;p&gt;接口行为刻意模拟真实业务：每次请求先做 50ms 的下游 IO 等待（如调用外部服务），再返回 2KB 响应体，几乎不消耗 CPU。这正是虚拟线程最理想的画像——&lt;strong&gt;阻塞占比极高、计算占比极低&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;三种线程模型对照：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模型 A&lt;/strong&gt;：&lt;code&gt;newFixedThreadPool(200)&lt;/code&gt;，沿用旧架构的保守配置；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型 B&lt;/strong&gt;：&lt;code&gt;newFixedThreadPool(1000)&lt;/code&gt;，把线程数顶到接近硬件上限；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型 C&lt;/strong&gt;：&lt;code&gt;Executors.newVirtualThreadPerTaskExecutor()&lt;/code&gt;，每请求一虚拟线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-吞吐与延迟对比&#34;&gt;&lt;a href=&#34;#32-%e5%90%9e%e5%90%90%e4%b8%8e%e5%bb%b6%e8%bf%9f%e5%af%b9%e6%af%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.2 吞吐与延迟对比
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模型&lt;/th&gt;
					&lt;th&gt;峰值吞吐&lt;/th&gt;
					&lt;th&gt;P50 延迟&lt;/th&gt;
					&lt;th&gt;P99 延迟&lt;/th&gt;
					&lt;th&gt;200 并发下 CPU 占用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;固定线程池 200&lt;/td&gt;
					&lt;td&gt;3,102 req/s&lt;/td&gt;
					&lt;td&gt;62ms&lt;/td&gt;
					&lt;td&gt;138ms&lt;/td&gt;
					&lt;td&gt;约 15%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;固定线程池 1000&lt;/td&gt;
					&lt;td&gt;15,240 req/s&lt;/td&gt;
					&lt;td&gt;51ms&lt;/td&gt;
					&lt;td&gt;96ms&lt;/td&gt;
					&lt;td&gt;约 60%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;虚拟线程&lt;/td&gt;
					&lt;td&gt;18,860 req/s&lt;/td&gt;
					&lt;td&gt;46ms&lt;/td&gt;
					&lt;td&gt;63ms&lt;/td&gt;
					&lt;td&gt;约 55%&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;三组数据放在一起，信息量很大：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;虚拟线程吞吐最高&lt;/strong&gt;，比 200 线程池提升约 6 倍，比 1000 线程池仍高出约 24%；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P99 延迟显著更稳&lt;/strong&gt;。固定线程池到 1000 线程时，调度器和 GC 压力已经让长尾明显变差，虚拟线程的 P99 反而从 96ms 收窄到 63ms；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU 占用并不比 1000 线程池高&lt;/strong&gt;。虚拟线程把阻塞等待让给了载体线程（carrier thread），同样的 CPU 预算换来更高的有效吞吐。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;33-内存占用对比&#34;&gt;&lt;a href=&#34;#33-%e5%86%85%e5%ad%98%e5%8d%a0%e7%94%a8%e5%af%b9%e6%af%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.3 内存占用对比
&lt;/h3&gt;&lt;p&gt;线程是平台线程的&amp;quot;大头开销&amp;quot;之一，实测在压测过程中采集常驻内存：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模型&lt;/th&gt;
					&lt;th&gt;活跃线程/协程数&lt;/th&gt;
					&lt;th&gt;常驻内存&lt;/th&gt;
					&lt;th&gt;备注&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;固定线程池 200&lt;/td&gt;
					&lt;td&gt;200&lt;/td&gt;
					&lt;td&gt;约 1.1GB&lt;/td&gt;
					&lt;td&gt;线程栈约 200MB，其余为业务对象&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;固定线程池 1000&lt;/td&gt;
					&lt;td&gt;1000&lt;/td&gt;
					&lt;td&gt;约 1.9GB&lt;/td&gt;
					&lt;td&gt;栈内存占比明显上升&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;虚拟线程&lt;/td&gt;
					&lt;td&gt;约 2.4 万&lt;/td&gt;
					&lt;td&gt;约 1.2GB&lt;/td&gt;
					&lt;td&gt;虚拟线程栈随用随回收，几乎可忽略&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;一组直观的数字：单条平台线程默认栈 1MB，而一条虚拟线程栈在空闲时可被 GC 回收，占用通常在 KB 级。1 万个虚拟线程的内存开销，往往还抵不过 1 千个平台线程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;结论很清晰：&lt;strong&gt;在 IO 密集的 HTTP 场景，虚拟线程同时赢下吞吐、延迟和内存三项指标&lt;/strong&gt;，属于教科书式的适用场景。&lt;/p&gt;
&lt;h3 id=&#34;34-并发度爬坡观察&#34;&gt;&lt;a href=&#34;#34-%e5%b9%b6%e5%8f%91%e5%ba%a6%e7%88%ac%e5%9d%a1%e8%a7%82%e5%af%9f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;3.4 并发度爬坡观察
&lt;/h3&gt;&lt;p&gt;除了稳态数字，爬坡过程同样有信息量。我们用 wrk 从 50 并发逐步加到 2000 并发，观察三种模型的吞吐曲线：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;并发数&lt;/th&gt;
					&lt;th&gt;固定线程池 200&lt;/th&gt;
					&lt;th&gt;固定线程池 1000&lt;/th&gt;
					&lt;th&gt;虚拟线程&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;50&lt;/td&gt;
					&lt;td&gt;940 req/s&lt;/td&gt;
					&lt;td&gt;940 req/s&lt;/td&gt;
					&lt;td&gt;942 req/s&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;500&lt;/td&gt;
					&lt;td&gt;3,020 req/s&lt;/td&gt;
					&lt;td&gt;4,860 req/s&lt;/td&gt;
					&lt;td&gt;7,210 req/s&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1000&lt;/td&gt;
					&lt;td&gt;3,100 req/s&lt;/td&gt;
					&lt;td&gt;9,900 req/s&lt;/td&gt;
					&lt;td&gt;14,500 req/s&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2000&lt;/td&gt;
					&lt;td&gt;3,098 req/s&lt;/td&gt;
					&lt;td&gt;15,100 req/s&lt;/td&gt;
					&lt;td&gt;18,830 req/s&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;低并发下三者几乎没差别，因为线程数都足够应付；&lt;strong&gt;一旦并发超过平台线程数，固定线程池的吞吐立刻封顶并开始排队，而虚拟线程仍能平滑跟涨&lt;/strong&gt;。这正是&amp;quot;线程池需要靠调参猜容量、虚拟线程天然弹性&amp;quot;的本质差异——前者是预分配，后者是按需生长。&lt;/p&gt;
&lt;h2 id=&#34;四场景二kafka-消息消费&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e5%9c%ba%e6%99%af%e4%ba%8ckafka-%e6%b6%88%e6%81%af%e6%b6%88%e8%b4%b9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、场景二：Kafka 消息消费
&lt;/h2&gt;&lt;h3 id=&#34;41-测试设计&#34;&gt;&lt;a href=&#34;#41-%e6%b5%8b%e8%af%95%e8%ae%be%e8%ae%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;4.1 测试设计
&lt;/h3&gt;&lt;p&gt;消息消费和 HTTP 有个本质差异：&lt;strong&gt;Kafka 的消费进度是按分区管理的，天然有并发上限&lt;/strong&gt;。主题 3 个分区，每条消息处理含 20ms 模拟业务 IO 加少量 JSON 反序列化 CPU。&lt;/p&gt;
&lt;p&gt;这里设计了三种消费策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;策略 A&lt;/strong&gt;：单消费者线程按分区同步处理（最朴素的写法，消费线程数 = 分区数）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略 B&lt;/strong&gt;：每分区一个平台线程 Worker，消息投递到本地队列，Worker 并发处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略 C&lt;/strong&gt;：单消费者线程拉取消息，每条消息提交给虚拟线程处理，处理完成后再提交 offset。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-消费速率对比&#34;&gt;&lt;a href=&#34;#42-%e6%b6%88%e8%b4%b9%e9%80%9f%e7%8e%87%e5%af%b9%e6%af%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;4.2 消费速率对比
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;消费速率&lt;/th&gt;
					&lt;th&gt;单条处理 P99&lt;/th&gt;
					&lt;th&gt;堆积恢复速度&lt;/th&gt;
					&lt;th&gt;代码复杂度&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;单线程同步&lt;/td&gt;
					&lt;td&gt;1,180 msg/s&lt;/td&gt;
					&lt;td&gt;102ms&lt;/td&gt;
					&lt;td&gt;最慢&lt;/td&gt;
					&lt;td&gt;最低&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;平台线程 Worker&lt;/td&gt;
					&lt;td&gt;8,640 msg/s&lt;/td&gt;
					&lt;td&gt;78ms&lt;/td&gt;
					&lt;td&gt;较快&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;虚拟线程逐条处理&lt;/td&gt;
					&lt;td&gt;17,900 msg/s&lt;/td&gt;
					&lt;td&gt;61ms&lt;/td&gt;
					&lt;td&gt;最快&lt;/td&gt;
					&lt;td&gt;低&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;策略 C 的写法其实很&amp;quot;朴素&amp;rdquo;，核心代码只有几行：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;executor&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Executors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ConsumerRecord&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;batch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pollBatch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Future&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;?&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;futures&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;batch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;stream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;executor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;submit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;toList&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;futures&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 全部处理完再提交 offset&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;consumer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;commitSync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它把&amp;quot;手动管理线程 + 队列 + 信号量&amp;quot;的样板代码几乎都省掉了，吞吐却接近平台线程 Worker 的两倍。&lt;code&gt;try-with-resources&lt;/code&gt; 保证了这批任务结束后 Executor 自动关闭，不会让虚拟线程泄漏到下一个批次里。&lt;/p&gt;
&lt;h3 id=&#34;43-必须注意的两个前提&#34;&gt;&lt;a href=&#34;#43-%e5%bf%85%e9%a1%bb%e6%b3%a8%e6%84%8f%e7%9a%84%e4%b8%a4%e4%b8%aa%e5%89%8d%e6%8f%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;4.3 必须注意的两个前提
&lt;/h3&gt;&lt;p&gt;第一，&lt;strong&gt;offset 提交语义&lt;/strong&gt;。虚拟线程并发处理时，必须等一批消息全部处理完再提交 offset，否则部分成功、部分失败会丢消息。我们用的&amp;quot;提交任务→统一等待→提交 offset&amp;quot;模式，把&amp;quot;至少一次&amp;quot;语义保持得很好。&lt;/p&gt;
&lt;p&gt;第二，&lt;strong&gt;分区数是硬上限&lt;/strong&gt;。虚拟线程再多，消息也只能按分区顺序消费，单分区的吞吐不会因为虚拟线程而提升。本测试吞吐翻倍，靠的是把&amp;quot;单线程阻塞处理&amp;quot;变成&amp;quot;多虚拟线程并行处理&amp;rdquo;，而不是突破分区限制。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;换句话说：虚拟线程解决的是&amp;quot;处理并发度&amp;quot;，解决不了&amp;quot;分区并行度&amp;quot;。上游主题分区不够，虚拟线程也帮不上忙——这是很多团队改造后收益不及预期的头号原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;44-消费侧参数配合&#34;&gt;&lt;a href=&#34;#44-%e6%b6%88%e8%b4%b9%e4%be%a7%e5%8f%82%e6%95%b0%e9%85%8d%e5%90%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;4.4 消费侧参数配合
&lt;/h3&gt;&lt;p&gt;虚拟线程并发处理后，消费者端的参数也要跟着调整，否则吞吐会被拉取环节卡住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max.poll.records&lt;/code&gt;：建议从默认 500 适当调大，一次拉取更多的记录，让虚拟线程并行窗口更宽，减少拉取等待的占比；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max.poll.interval.ms&lt;/code&gt;：虚拟线程并发后单批处理时间变长是正常的，&lt;code&gt;max.poll.interval.ms&lt;/code&gt; 要相应放宽，避免触发 &lt;code&gt;PollTimeoutException&lt;/code&gt; 导致 rebalance；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetch.max.bytes&lt;/code&gt;：配合 &lt;code&gt;max.poll.records&lt;/code&gt; 调大，减少网络往返次数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enable.auto.commit&lt;/code&gt;：强烈建议关闭，改为上面的&amp;quot;批处理完成后手动 &lt;code&gt;commitSync()&lt;/code&gt;&amp;ldquo;模式，避免 offset 漂移造成重复或丢失。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;经验值：虚拟线程把单条处理延迟从 100ms 压到 60ms 后，&lt;code&gt;max.poll.interval.ms&lt;/code&gt; 若仍用默认的 5 分钟一般够用；但若单批上千条，建议显式设到 10 分钟以上，并配合监控观察 rebalance 频率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;五优势边界io-密集与-cpu-密集的分水岭&#34;&gt;&lt;a href=&#34;#%e4%ba%94%e4%bc%98%e5%8a%bf%e8%be%b9%e7%95%8cio-%e5%af%86%e9%9b%86%e4%b8%8e-cpu-%e5%af%86%e9%9b%86%e7%9a%84%e5%88%86%e6%b0%b4%e5%b2%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、优势边界：IO 密集与 CPU 密集的分水岭
&lt;/h2&gt;&lt;p&gt;为了把边界划清楚，我们又补了一组纯 CPU 密集的对照测试：同样的计算任务（如哈希碰撞模拟、加密运算），不做任何 IO。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;执行方式&lt;/th&gt;
					&lt;th&gt;完成耗时（相对值）&lt;/th&gt;
					&lt;th&gt;CPU 利用率&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;平台线程池（线程数=核数）&lt;/td&gt;
					&lt;td&gt;1.00×&lt;/td&gt;
					&lt;td&gt;接近 100%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;平台线程池（线程数=4×核数）&lt;/td&gt;
					&lt;td&gt;0.98×&lt;/td&gt;
					&lt;td&gt;约 100%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;虚拟线程&lt;/td&gt;
					&lt;td&gt;1.08×&lt;/td&gt;
					&lt;td&gt;约 95%&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以看到，&lt;strong&gt;纯 CPU 场景下虚拟线程不仅没有增益，反而有约 8% 的损耗&lt;/strong&gt;（调度开销）。原因是虚拟线程的调度依赖 ForkJoinPool 载体线程，而载体线程数默认等于可用核心数，再叠加虚拟线程自身的挂起/恢复成本，纯计算场景反而拖了后腿。&lt;/p&gt;
&lt;p&gt;由此可以总结出虚拟线程的优势边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IO 密集（阻塞占比 &amp;gt; 70%）&lt;/strong&gt;：收益最大，吞吐可提升数倍，强烈推荐；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合负载（IO 与 CPU 各半）&lt;/strong&gt;：有正收益，但要控制并发虚拟线程总量，避免把 CPU 打满后互相拖累；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯 CPU 密集&lt;/strong&gt;：无收益甚至有轻微损耗，请继续使用平台线程池或 ForkJoinPool；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高频短任务（微秒级）&lt;/strong&gt;：调度开销占比高，收益不明显，需要实测验证。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;51-如何量化阻塞占比&#34;&gt;&lt;a href=&#34;#51-%e5%a6%82%e4%bd%95%e9%87%8f%e5%8c%96%e9%98%bb%e5%a1%9e%e5%8d%a0%e6%af%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;5.1 如何量化&amp;quot;阻塞占比&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;判断一段代码适不适合虚拟线程，别靠感觉，可以用两个低成本的量化手段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;看 CPU 利用率&lt;/strong&gt;：压测时若单核 CPU 长期低于 60%，而吞吐已经上不去，说明瓶颈大概率在阻塞等待而非计算，这类场景改造收益明显；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;看线程等待时间&lt;/strong&gt;：用 &lt;code&gt;jcmd Thread.print&lt;/code&gt; 或 APM 的线程状态统计，统计 &lt;code&gt;WAITING/TIMED_WAITING&lt;/code&gt; 状态占比。阻塞占比超过 70% 的接口，往往是虚拟线程改造的首选对象。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;一个反例：有个内部批处理任务号称&amp;quot;IO 密集&amp;quot;，改造后收益为零。排查发现 90% 的时间花在内存排序上，纯计算。所以&lt;strong&gt;先量化，再改造&lt;/strong&gt;，别让直觉替数据做决定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;六踩坑记录与参数调优&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95%e4%b8%8e%e5%8f%82%e6%95%b0%e8%b0%83%e4%bc%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、踩坑记录与参数调优
&lt;/h2&gt;&lt;p&gt;改造过程并非一帆风顺，几个坑比较典型，值得单独拿出来说。&lt;/p&gt;
&lt;h3 id=&#34;61-坑一synchronized-导致的-pinning&#34;&gt;&lt;a href=&#34;#61-%e5%9d%91%e4%b8%80synchronized-%e5%af%bc%e8%87%b4%e7%9a%84-pinning&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;6.1 坑一：synchronized 导致的 pinning
&lt;/h3&gt;&lt;p&gt;首轮压测时，我们在业务代码里给一个缓存对象加了 &lt;code&gt;synchronized&lt;/code&gt; 保护。结果虚拟线程组吞吐直接腰斩，比平台线程组还低。原因是虚拟线程在 &lt;code&gt;synchronized&lt;/code&gt; 块内发生阻塞时，会&lt;strong&gt;钉住（pin）载体线程&lt;/strong&gt;，导致载体线程被占着干等。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;排查手法：压测时开启 &lt;code&gt;-Djdk.tracePinnedThreads=full&lt;/code&gt;，日志里会打印 pinning 调用栈，一眼就能定位到 &lt;code&gt;synchronized&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;修复方案很简单：把 &lt;code&gt;synchronized&lt;/code&gt; 换成 &lt;code&gt;ReentrantLock&lt;/code&gt; 或 &lt;code&gt;StampedLock&lt;/code&gt;。JDK 后续版本已改善部分阻塞场景的 pinning，但在 JDK 23 上，&lt;strong&gt;&amp;ldquo;锁内不要做阻塞 IO&amp;quot;仍是最稳妥的纪律&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;62-坑二threadlocal-与池化复用冲突&#34;&gt;&lt;a href=&#34;#62-%e5%9d%91%e4%ba%8cthreadlocal-%e4%b8%8e%e6%b1%a0%e5%8c%96%e5%a4%8d%e7%94%a8%e5%86%b2%e7%aa%81&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;6.2 坑二：ThreadLocal 与池化复用冲突
&lt;/h3&gt;&lt;p&gt;虚拟线程由 JVM 调度复用在载体线程上，&lt;code&gt;ThreadLocal&lt;/code&gt; 的&amp;quot;线程隔离&amp;quot;语义仍然成立（JVM 会为每个虚拟线程维护独立副本），但&lt;strong&gt;大量使用 ThreadLocal 会显著增加虚拟线程的挂起/恢复开销&lt;/strong&gt;。我们的做法：连接、日期格式化这类重对象改用轻量对象池或直接参数传递，把 ThreadLocal 的使用面压到最小。&lt;/p&gt;
&lt;h3 id=&#34;63-坑三连接池参数不用再按线程数配&#34;&gt;&lt;a href=&#34;#63-%e5%9d%91%e4%b8%89%e8%bf%9e%e6%8e%a5%e6%b1%a0%e5%8f%82%e6%95%b0%e4%b8%8d%e7%94%a8%e5%86%8d%e6%8c%89%e7%ba%bf%e7%a8%8b%e6%95%b0%e9%85%8d&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;6.3 坑三：连接池参数不用再&amp;quot;按线程数配&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;老经验是&amp;quot;连接池大小 = 线程数&amp;quot;，虚拟线程下这套就失效了——并发虚拟线程可能上万，数据库连接却不可能开上万个。正确姿势是把连接池控制在 &lt;code&gt;2 × 核数&lt;/code&gt; 到 &lt;code&gt;4 × 核数&lt;/code&gt;，让虚拟线程在获取连接时短暂等待即可，这是虚拟线程最擅长应付的阻塞。&lt;/p&gt;
&lt;h3 id=&#34;64-坑四观测与调试的盲区&#34;&gt;&lt;a href=&#34;#64-%e5%9d%91%e5%9b%9b%e8%a7%82%e6%b5%8b%e4%b8%8e%e8%b0%83%e8%af%95%e7%9a%84%e7%9b%b2%e5%8c%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;6.4 坑四：观测与调试的盲区
&lt;/h3&gt;&lt;p&gt;虚拟线程数量可能达到数十万，传统监控三板斧会失效一半：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程 dump 会爆炸&lt;/strong&gt;：&lt;code&gt;jstack&lt;/code&gt; 一次性把几万个虚拟线程栈全打出来，文件动辄几十 MB，根本没法看。建议改用 &lt;code&gt;jcmd Thread.dump_to_file&lt;/code&gt; 并按 &lt;code&gt;-Djdk.tracePinnedThreads&lt;/code&gt; 的组合，只关注 pinning 和有问题的线程；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JMX 的线程指标失真&lt;/strong&gt;：&lt;code&gt;ThreadMXBean&lt;/code&gt; 默认统计的是平台线程，需要额外开启虚拟线程监控属性才能看到真实情况；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志中的线程名要区分&lt;/strong&gt;：虚拟线程默认名字形如 &lt;code&gt;virtual-123&lt;/code&gt;，排查时要和业务线程名（如 &lt;code&gt;kafka-consumer-0&lt;/code&gt;）区分开，避免日志上下文串线。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们的做法是统一封装一层&amp;quot;业务线程名 + 虚拟线程 ID&amp;quot;的 MDC 前缀，让日志既能按业务维度聚合，也能按虚拟线程维度下钻。&lt;/p&gt;
&lt;h3 id=&#34;65-参数调优清单&#34;&gt;&lt;a href=&#34;#65-%e5%8f%82%e6%95%b0%e8%b0%83%e4%bc%98%e6%b8%85%e5%8d%95&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;6.5 参数调优清单
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-Djdk.virtualThreadScheduler.parallelism=N&lt;/code&gt;：显式控制载体线程数，默认为核心数。纯 IO 场景可保持默认，混合场景适当调大；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Djdk.virtualThreadScheduler.maxPoolSize=N&lt;/code&gt;：限制载体线程池上限，防止平台线程数失控；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Djdk.tracePinnedThreads=full&lt;/code&gt;：诊断 pinning 的开关，压测期必开、生产慎用（有性能损耗）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Xss&lt;/code&gt; 无需再为虚拟线程调大，虚拟线程栈由堆管理，独立于该参数；&lt;/li&gt;
&lt;li&gt;配合 &lt;code&gt;-XX:+UseZGC&lt;/code&gt; 或 G1 的适当堆设置，虚拟线程场景下的大堆压力通常比平台线程场景更低。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;七结论与建议&#34;&gt;&lt;a href=&#34;#%e4%b8%83%e7%bb%93%e8%ae%ba%e4%b8%8e%e5%bb%ba%e8%ae%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;七、结论与建议
&lt;/h2&gt;&lt;p&gt;回到最初的问题：虚拟线程的边界在哪里？用这组压测数据可以给出一个明确的回答。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它真正强大、也最值得用力的场景，是阻塞型 IO 密集负载&lt;/strong&gt;——HTTP 短请求服务、RPC 调用方、消息消费端处理、批量任务调度。在这些场景里，虚拟线程能以更少的资源拿回更高的吞吐和更稳的延迟，改造收益立竿见影。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它无能为力的场景同样清晰&lt;/strong&gt;：纯 CPU 计算、高频微任务、以及受分区数限制的顺序消费。这些场景请继续用平台线程池或 ForkJoinPool，别让&amp;quot;赶时髦&amp;quot;付出无谓的调度成本。&lt;/p&gt;
&lt;p&gt;改造也不是改一行 Executor 就完事：&lt;code&gt;synchronized&lt;/code&gt; 的 pinning、ThreadLocal 的滥用、连接池参数的老经验，每一项都需要在新线程模型下重新校准。建议任何团队在正式上线前，都先跑一组和本测试同构的对照实验，用数据确认收益在自己业务里真实存在。&lt;/p&gt;
&lt;p&gt;技术选型从来不是&amp;quot;新的一定更好&amp;quot;，而是&amp;quot;在正确的边界内使用正确的工具&amp;quot;。虚拟线程把 Java 并发编程的下限抬高了一大截，剩下的，就交给架构师去划清那条边界了。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
