虚拟线程在synchronized块中会触发Pinning,被绑定至平台线程而无法调度;应缩小临界区、改用JUC原子类或并发集合、细化锁粒度,并通过JVM参数或JFR监控高频Pinning。

Java虚拟线程(Virtual Thread)在遇到 synchronized 块时会触发“固定”(Pinning),即当前虚拟线程被绑定到其正在运行的平台线程(Platform Thread),无法被调度器挂起或迁移。这是JVM为保证同步语义正确性而采取的安全机制,但可能削弱虚拟线程的高并发优势。解决的关键不是“避免synchronized”,而是**减少固定范围、缩短临界区、用更轻量的替代方案**。
理解Pinning发生时机与影响
虚拟线程在以下情况会被Pinning:
- 进入任意
synchronized方法或代码块(包括隐式锁如String.intern()、Object.wait()等) - 调用本地方法(JNI)且未声明为
native synchronized的变体(极少) - 使用
Thread.sleep()、Object.wait()等阻塞操作(注意:这些本身也会Pin,但JDK 21+已对部分做优化)
Pinning本身不报错,但会导致:同一平台线程无法被其他虚拟线程复用,若大量虚拟线程因同步块堆积在少数平台线程上,可能引发吞吐下降或响应延迟升高。
优先用java.util.concurrent中的无锁/协作式工具
多数场景下,synchronized 并非唯一选择。推荐用JUC中专为高并发设计的替代方案:
立即学习“Java免费学习笔记(深入)”;
-
Atomic类:如
AtomicInteger、AtomicReference替代简单计数或状态更新 -
Lock接口实现:如
ReentrantLock本身仍会Pin(因底层依赖monitor),但可配合tryLock(long, TimeUnit)实现超时退出,避免无限等待 -
并发集合:用
ConcurrentHashMap、CopyOnWriteArrayList替代加锁的HashMap + synchronized -
Structured Concurrency + Scoped Values(JDK 22+):用
ScopedValue替代线程局部状态共享,避免因同步访问ThreadLocal引发的间接Pinning
缩小synchronized作用域并避免嵌套
如果必须用 synchronized,应严格限制其范围:
- 只包裹真正需要互斥的最小代码段,不要把I/O、网络调用、日志打印等耗时操作包进去
- 避免在synchronized块内调用可能再次触发Pinning的方法(如另一个synchronized方法、
wait()、System.currentTimeMillis()虽不Pin但易误判) - 考虑将锁对象粒度细化:用独立的私有final锁对象,而非
this或类.class,降低锁竞争面
例如:
❌ 不推荐:synchronized (this) {
log.info("start process");
int result = heavyCompute(); // 耗时计算
db.save(result); // I/O
}
✅ 推荐:
private final Object computeLock = new Object();
// ...
int result;
synchronized (computeLock) {
result = fastStateUpdate(); // 快速、纯内存操作
}
log.info("computed: {}", result);
db.save(result); // 移出同步块
监控与诊断Pinning行为
可通过JVM参数和工具确认是否发生不期望的Pinning:
- 启动时添加
-Djdk.virtualThreadPinnedMonitor=debug,JVM会在每次Pinning时输出日志(含线程名、栈顶方法) - 使用
jcmd <pid> VM.native_memory summary观察平台线程数量是否异常增长 - JFR(Java Flight Recorder)录制中启用
jdk.VirtualThreadPinned事件,分析Pinning热点方法
注意:少量Pinning属正常(如初始化、日志框架内部),关注的是高频、长时、批量发生的Pinning。


















