当前位置:首页 > 文章列表 > 文章 > java教程 > Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势怎样在批处理代码中减少额外分配

Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势怎样在批处理代码中减少额外分配

来源:17golang原创 2026-11-10 04:35:42 0浏览 收藏

同步块导致虚拟线程占住载体的结论必须加上版本条件: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。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期
上一篇
Java 虚拟线程 pinned同步块为什么让虚拟线程失去并发优势为什么结果为空或不符合预期
Java record 反射怎样读取 record 组件并保持字段顺序怎样完成一个可复用的最小示例
下一篇
Java record 反射怎样读取 record 组件并保持字段顺序怎样完成一个可复用的最小示例
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    965次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    966次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1077次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1057次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    888次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码