Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势怎样在批处理代码中减少额外分配
同步块导致虚拟线程占住载体的结论必须加上版本条件:JDK 21–23 中,持有 synchronized 监视器时阻塞可能出现 pinning;JDK 24 引入 JEP 491 后,普通监视器相关阻塞不再因这一原因固定载体。但共享锁造成的业务串行仍然存在。减少批处理分配则要从对象所有权入手,让缓冲区在一个批次内复用,而不是让所有虚拟线程共用可变对象。
官方变更地址:https://www.oracle.com/java/technologies/javase/24-relnote-issues.html。OpenJDK 提案地址:https://openjdk.org/jeps/491。下面讨论这一已落地的变化怎样影响现有代码,不把它当作刚发布的消息。
1. 先按 JDK 版本区分 pinning 与锁竞争
虚拟线程借用载体线程执行;监视器保护共享状态。旧版本中,虚拟线程在同步块内进行某些阻塞调用时,无法像普通可卸载等待那样释放载体。高并发下,有限载体被长时间占用,就会削弱虚拟线程适合等待密集任务的优势。同步块本身不是错误,关键是版本、持锁范围与阻塞时长。

| 情况 | 应当判断什么 |
|---|---|
| JDK 21–23 持监视器阻塞 | 可能固定载体,优先关注频繁的长等待 |
| JDK 24 普通 synchronized 阻塞 | JEP 491 已改变监视器相关固定行为 |
| 所有版本共用一把业务锁 | 锁内工作仍互斥,吞吐可能受串行段限制 |
| 主要时间用于 CPU 运算 | 虚拟线程不增加可用 CPU 算力 |
原生代码等其他限制不能由“synchronized 已改善”推导为全部消失。实际发行版有回移差异时,应以该构建说明为准。
2. 用短临界区隔离批处理的阻塞工作
如果锁只保护提交计数,就让编码、等待与提交分开:批次任务持有输入列表和私有缓冲,编码函数写入私有缓冲;提交锁只保护共享计数。以下示例可用于 Java 21 及以上环境,保存为 BatchPacking.java。本文没有执行该程序,也不提供虚构的性能数字。
import java.util.List;
public class BatchPacking {
private final Object commitLock = new Object();
private long committedChars;
public void pack(List rows) throws InterruptedException {
// 每个批次独占一个缓冲区,避免每行都新建构建器
StringBuilder buffer = new StringBuilder(4096);
for (String row : rows) {
buffer.setLength(0);
encode(row, buffer);
int chars = buffer.length();
// 模拟阻塞边界,实际项目替换为有期限的客户端调用
Thread.sleep(1);
// 仅提交共享状态,阻塞调用不持有提交锁
synchronized (commitLock) {
committedChars += chars;
}
}
}
private static void encode(String row, StringBuilder out) {
// 追加到已有对象;本例只演示缓冲所有权,不定义网络协议
out.append("row=").append(row);
}
public long committedChars() {
// 读取与更新使用相同监视器,保证可见性
synchronized (commitLock) {
return committedChars;
}
}
public static void main(String[] args) throws InterruptedException {
BatchPacking job = new BatchPacking();
Thread task = Thread.ofVirtual().start(() -> {
try {
job.pack(List.of("alpha", "beta", "gamma"));
} catch (InterruptedException e) {
// 保留中断信号,让调用方能够结束批次
Thread.currentThread().interrupt();
}
});
task.join();
System.out.println(job.committedChars());
}
} # 使用目标 JDK 编译、运行相同源文件,避免混用运行时 javac BatchPacking.java java BatchPacking
该程序按编码后的字符数累计,语义上总计为 26。Thread.sleep 仅模拟等待;它不是网络调用的性能替身,也不能据此计算线上提速倍数。若业务要求外部请求与状态更新具有原子性,不能直接把请求移出锁;需要版本号、事务或幂等提交等适合业务的并发协议。
3. 将缓冲区限定为批次私有对象
私有缓冲是每次 pack 调用创建一次的 StringBuilder;setLength(0) 清除逻辑内容以供下一行使用。这样减少的是逐行创建构建器的机会,未声称整个编码过程零分配。输入 String、列表、虚拟线程以及真实客户端内部仍可能产生对象。

不要把它改成静态共享构建器,否则多个线程会相互覆盖;给它加一把全局锁又会让编码串行。也不要为了复用大缓冲盲目迁移到 ThreadLocal:大量短生命周期虚拟线程可能各自持有一份大对象,失去平台线程池长期复用时的成本优势。
4096 是示例初始容量而非推荐阈值。缓冲扩容后,清空长度不会主动缩小容量;遇到异常大行,可以按业务上限重新创建缓冲或拒绝超限输入。若异步客户端保留传入对象,必须等待消费结束或传递独立不可变副本,不能立即清空复用。若需要字节数,应以明确字符集编码后的长度统计,字符数不能充当传输字节数。
4. 比较方案时同时观察等待与分配
对旧方案、短临界区方案和升级后的运行时使用相同数据量、等待分布、连接池限制和并发上限。分别观察完成吞吐、尾部耗时、锁竞争、载体占用、分配速率、GC 与峰值存活内存。JDK 21–23 可结合 JFR 的虚拟线程固定事件定位长期等待;JDK 24 的监视器实现已变化,不能因为旧固定事件减少,就认定锁竞争也减少。
分配优化和 pinning 优化应分别归因:前者涉及每行对象与缓冲保留,后者涉及阻塞期间的调度限制。升级 JDK 仍须兼顾依赖和运维兼容;改变锁实现也必须保持原有互斥、可见性与异常释放语义。
升级后还要全部替换 synchronized 吗?
不需要机械替换。先依据目标运行时和等待热点判断,短小的共享状态更新可以继续使用监视器。
复用 StringBuilder 是否必然更快?
不保证。小对象可能被编译器优化;大缓冲保留也可能增加内存成本。用实际负载比较分配与存活内存。
一个虚拟线程处理无限大的批次合适吗?
应按资源预算划分批次,控制输入保留、客户端并发与任务期限。轻量线程不代表数据库连接或下游服务也无限可用。
虚拟线程原始设计及线程本地资源说明:https://openjdk.org/jeps/444;Oracle 核心库指南:https://docs.oracle.com/en/java/javase/22/core/java-core-libraries-developer-guide.pdf。
Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期
- 上一篇
- Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期
- 下一篇
- Java record 反射怎样读取 record 组件并保持字段顺序怎样完成一个可复用的最小示例
-
- 文章 · java教程 | 11分钟前 | 反射 · Java · java 反射 字段顺序 record RecordComponent getRecordComponents
- Java record 反射怎样读取 record 组件并保持字段顺序怎样完成一个可复用的最小示例
- 199浏览 收藏
-
- 文章 · java教程 | 17分钟前 | 性能排查 · 虚拟线程 · java synchronized reentrantlock 虚拟线程 JFR pinned
- Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期
- 495浏览 收藏
-
- 文章 · java教程 | 21分钟前 |
- Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势怎样完成一个可复用的最小示例
- 420浏览 收藏
-
- 文章 · java教程 | 22分钟前 |
- Java Phaser 动态注册参与者并推进阶段同步的实现方法
- 368浏览 收藏
-
- 文章 · java教程 | 26分钟前 |
- CompletableFuture 超时怎样让超时任务停止等待并返回备用值为什么结果为空或不符合预期
- 339浏览 收藏
-
- 文章 · java教程 | 29分钟前 |
- CompletableFuture 超时怎样让超时任务停止等待并返回备用值怎样完成一个可复用的最小示例
- 298浏览 收藏
-
- 文章 · java教程 | 18小时前 | Java · JVM ·
- JVM Class Data Sharing怎样生成类数据归档缩短启动时间为什么结果为空或不符合预期
- 320浏览 收藏
-
- 文章 · java教程 | 2天前 | 字符串处理 · java 分隔符 token 字符串解析 StringTokenizer returnDelims
- Java StringTokenizer 怎样把分隔符也作为 token 返回
- 318浏览 收藏
-
- 文章 · java教程 | 2天前 |
- Java 26 API 文档怎样按模块查找类
- 427浏览 收藏
-
- 文章 · java教程 | 2天前 |
- Java StringJoiner merge 为什么不保留对方前后缀
- 391浏览 收藏
-
- 文章 · java教程 | 2天前 |
- Java NIO FileChannel 怎样安全写入固定偏移
- 346浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 965次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 966次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1077次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1057次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 888次使用
-
- Java 时间格式兼容多个输入格式的方法
- 2026-10-18 501浏览
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览

