Java 21虚拟线程中synchronized本身不会触发Pinning;真正引发Pinning的是JNI调用、阻塞本地方法或需绑定OS线程的操作,synchronized仅依赖JVM内部monitor机制,属纯Java层协作,不强制绑定平台线程。

Java 21 的虚拟线程(Virtual Threads)默认是“可迁移的”(migratable),即运行过程中可以被调度器从一个平台线程(OS 线程)切换到另一个平台线程,以提升吞吐量。但 synchronized 块或方法本身**不会主动触发 Thread Pinning**(线程钉住),也不会强制将虚拟线程绑定到某个固定平台线程上。
synchronized 不会自动引发 Pinning
虚拟线程在进入 synchronized 临界区时,不会自动钉住(pin)当前平台线程。JVM(特别是 OpenJDK 21+)对虚拟线程做了深度优化:即使持有 monitor 锁,只要不涉及阻塞式 I/O、JNI 或其他必须保持线程身份的操作,虚拟线程仍可在锁等待期间被挂起、迁移甚至复用平台线程。
也就是说:
- 普通 synchronized 代码块 → 不触发 pinning
- 虚拟线程在锁竞争中被 park → 仍可迁移(除非底层 monitor 实现因特定场景隐式 pin,但这不是规范行为,也不应依赖)
真正触发 Pinning 的场景是 JNI 或阻塞本地调用
Thread Pinning 是 JVM 在检测到虚拟线程执行某些“不可迁移操作”时的被动保护机制,典型包括:
- 调用
native方法(尤其是未标注@Native且未声明为CarrierThread-safe 的 JNI 函数) - 使用
java.lang.Thread.currentThread()并将其传递给 JNI(JVM 为保证 native 层看到的jthread与 OS 线程一致,会钉住) - 调用某些老式阻塞 API(如
Object.wait()在极少数 GC 配置下可能关联 pinning,但非常罕见)
synchronized 本身不属于这些场景。它基于 JVM 内部的 monitor 机制(通常由对象头 + 同步队列实现),完全在 Java 层协作完成,不涉及 native 线程身份绑定。
立即学习“Java免费学习笔记(深入)”;
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
如果需要显式 Pinning,得绕过虚拟线程语义
目前(Java 21)没有公开 API 让用户“手动钉住”虚拟线程。但以下方式会间接导致 pinning:
- 在
synchronized块内调用未经适配的 JNI 方法 → JVM 自动 pin - 使用
Thread.Builder.ofVirtual().allowSetThreadGroup(true)并配合Thread.currentThread().getThreadGroup()(不推荐,破坏虚拟线程轻量性) - 通过
ScopedValue或ThreadLocal与 native 层交互(若 native 侧缓存了jthread,JVM 可能被迫 pin)
注意:Pinning 会削弱虚拟线程优势(高并发、低内存占用),应避免刻意诱导。
验证是否发生 Pinning 的方法
可通过 JVM 参数和诊断工具观察:
- 启动时加
-XX:+UnlockDiagnosticVMOptions -XX:+PrintPinnedThreadStacks,当 pinning 发生时打印栈 - 使用
jstack <pid>查看线程状态:被 pin 的虚拟线程会显示carrier thread: ... (pinned) - 监控
jdk.VirtualThreadPinnedJFR 事件(需开启-XX:StartFlightRecording:settings=profile)
你会发现:仅含 synchronized 的纯 Java 代码几乎不会出现在这些日志里。

















