当前位置:首页 > 文章列表 > 文章 > java教程 > Java virtual thread 使用 synchronized 后为什么仍会阻塞平台线程

Java virtual thread 使用 synchronized 后为什么仍会阻塞平台线程

来源:17golang原创 2026-09-09 04:22:40 0浏览 收藏

很多人第一次把 Java virtual thread 接到旧的同步代码上,会看到“平台线程也被占住了”的现象,于是把原因简单归结为 synchronized。这个判断要先加上 JDK 版本:JDK 21–23 中,虚拟线程在同步块里执行阻塞操作,确实可能固定 carrier;从 JDK 24 开始,JEP 491 已让虚拟线程在常规监视器等待和持锁阻塞时释放底层平台线程。若 JDK 24+ 仍出现平台线程长时间阻塞,优先检查调用者是不是平台线程、是否进入 native/Foreign Function,或临界区里根本不是“等待”而是持续计算。

结论先说:synchronized 仍然会造成互斥和锁等待,但在 JDK 24+ 不应再把普通监视器竞争自动等同于 virtual thread pinning。先看 JFR 的阻塞原因和 JDK 版本,再决定是否缩小临界区或换成 ReentrantLock
要点速览
  • 锁等待是应用层语义,线程固定是运行时调度问题,两者不是同一个指标。
  • JDK 21–23 要警惕 synchronized 包住 I/O;JDK 24+ 普通 monitor 已支持释放 carrier。
  • JFR 的 jdk.VirtualThreadPinnedjcmd Thread.dump_to_file 比线程名更可靠。

先分清“锁等待”和“线程固定”

synchronized 做的第一件事是获取对象监视器。已有线程持锁时,后来者要等待;这说明共享资源有竞争,并不自动说明载体平台线程被永久占住。虚拟线程通常可以在阻塞 I/O 时从 carrier 卸载,让同一个 carrier 去运行别的虚拟线程,但某些运行时边界会阻止卸载。

可以把现象拆成四个问题:谁在等 monitor,谁持有 monitor;等待发生在虚拟线程还是平台线程;等待期间是否进入阻塞 I/O;JFR 是否记录了 pinned 事件。下面的关系图只表达静态边界,不代表执行时序。

虚拟线程任务、synchronized 监视器、载体平台线程、阻塞 I O 与 JFR 诊断的静态关系图
图1:把 synchronized 的锁等待与 carrier 固定放在两个边界中观察,避免把所有阻塞都归因于同一个问题。

JDK 版本会改变 synchronized 的判断

旧版本的典型风险是把慢 I/O 放进同步块:

public synchronized String loadProfile() throws IOException {
    // 旧版 JDK 中,这里的阻塞 I/O 可能固定虚拟线程的 carrier
    return httpClient.send(request, BodyHandlers.ofString()).body();
}

在 JDK 21–23,频繁且长时间的这种写法会减少可用 carrier,吞吐量随并发上升而变差。短暂的内存操作或只在启动阶段使用的 synchronized 通常不值得为了虚拟线程强行改写。

JDK 24 的变化是“同步而不固定”:虚拟线程可以在等待 monitor 时卸载,也可以在持有 monitor 的代码遇到可卸载的阻塞时释放平台线程。它没有取消 synchronized 的互斥、可见性或 happens-before 语义,也不代表锁内代码会变快。平台线程本身仍然不能像虚拟线程一样卸载;如果调用者就是平台线程,它等待 monitor 时当然会处于阻塞状态。

看到的现象更可能的含义先查什么
虚拟线程在 monitor 上等待锁竞争,JDK 24+ 不必然是 pinning持锁线程与临界区长度
carrier 长时间不释放旧 JDK、native/foreign function 或不可卸载边界JFR VirtualThreadPinned
平台线程处于 BLOCKED平台线程自己在等 monitor,或被锁内工作占用线程转储与调用栈

用 JFR 和 jcmd 定位真实阻塞点

不要只根据线程名里的 ForkJoinPoolcarrier 下结论。Oracle 文档给出的 JFR 事件 jdk.VirtualThreadPinned 默认在超过 20ms 时记录,并且会带出阻塞操作与原因。可以在复现窗口采集一段 recording,再筛选相关事件:

# 采集结束后,只打印虚拟线程相关事件
jfr print --events jdk.VirtualThreadPinned,jdk.VirtualThreadSubmitFailed recording.jfr

# 导出线程转储,保留平台线程与虚拟线程的调用栈
jcmd  Thread.dump_to_file -format=json threads.json

若事件原因指向 native 或 foreign function,换锁通常不是根治;应先把这段调用移出高频临界区,或评估它是否必须在虚拟线程中执行。若只有大量 monitor 竞争而没有 pinned 事件,问题更接近锁粒度、持锁时间或平台线程调用者。

按 JDK 版本和阻塞类型选择修复

修复顺序应当是“先证据,后替换”。对保护内存状态的短临界区,保留 synchronized 往往更清晰;对可能做慢 I/O 的高频路径,先缩小锁区:

public String loadProfile() throws IOException {
    String requestId;
    synchronized (stateLock) {
        // 只在锁内读取共享状态,不把网络调用带进临界区
        requestId = currentRequestId;
    }
    // 网络等待发生在锁外,减少其他任务的排队时间
    return httpClient.send(buildRequest(requestId), BodyHandlers.ofString()).body();
}

如果业务确实需要可中断、可超时或更灵活的锁策略,再考虑 ReentrantLock,并用 try/finally 保证释放:

lock.lock();
try {
    // 临界区只保留必须互斥的内存操作
    updateCacheEntry();
} finally {
    // 即使业务抛出异常,也必须归还锁
    lock.unlock();
}

不要为了追求“虚拟线程化”而给所有 synchronized 加一层锁,也不要用固定平台线程池假装限制外部服务并发;并发上限应由明确的信号量或资源池承担。

JDK 版本、synchronized、ReentrantLock、native 调用与 JFR 诊断的决策边界图
图2:版本差异只决定 synchronized 的 carrier pinning 行为,最终修复仍要回到阻塞类型和证据。

相关问题

JDK 24+ 还需要把 synchronized 全部换成 ReentrantLock 吗?

不需要。先用 JFR 证明存在频繁、长时间的 pinning,再针对慢 I/O 或不可控调用缩小临界区;短内存操作保留 synchronized 通常更简单。

为什么虚拟线程很多,但平台线程数量没有一起增加?

虚拟线程由 Java runtime 调度并复用少量 carrier。它的价值是提高等待型任务的并发吞吐,不是让每个任务都拥有一个新的 OS 线程。

看到 BLOCKED 就说明发生了 pinning 吗?

不是。BLOCKED 可能只是某个线程等待 monitor;pinning 要看虚拟线程是否在不可卸载边界中占住 carrier,JFR 的 jdk.VirtualThreadPinned 更适合做判断。

参考:Oracle Java SE 25 Virtual ThreadsJDK 24 Significant ChangesOpenJDK JEP 444

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go image/draw 怎么把缩略图裁成固定比例Go image/draw 怎么把缩略图裁成固定比例
上一篇
Go image/draw 怎么把缩略图裁成固定比例
Go 编译报 undefined: C 时怎么确认是不是漏了 cgo 文件
下一篇
Go 编译报 undefined: C 时怎么确认是不是漏了 cgo 文件
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    34次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    189次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    127次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    50次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    35次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码