← 每日面试题 学习看板

为什么虚拟线程不能用synchronized?

面试题/00java/02Java 并发编程/09编程实践与应用场景/为什么虚拟线程不能用synchronized?.md

面试记录 2026-10-26

题目:为什么虚拟线程不能用 synchronized?

难度:medium

标签:java/concurrency-practice, java/concurrency, general/knowledge, difficulty/medium

评分:⭐⭐⭐☆☆(3/5)

我的回答

虚拟线程不用 synchronized 的核心原因是 synchronized 是操作系统层面的锁,会直接将虚拟线程对应的系统线程锁住,这样最后会导致虚拟线程退化成常规线程,和系统线程一对一,会影响虚拟线程使用,可以用 java 的 ReentrantLock 来做锁操作。

点评

优点:

不足:

完整讲解(权威版)

最后更新:2026-10-26 · 基于第 1 次作答

L1 · 30 秒速记

结论:不是"synchronized 不能用",而是"别在 synchronized 块里阻塞"——阻塞时 carrier thread 被 pin。 关键词:pinning · Monitor owner 与 OS 线程绑定 · ReentrantLock uncontended 不 pin 边界:ReentrantLock 竞争时也 pin;彻底避免靠 CAS/无锁;JNI 也会 pin

L2 · 核心讲解

核心考点定位:能否准确理解 pinning 的机制(不是"退化为 OS 线程",而是 carrier thread 被固定),能否给出边界清晰的替代方案。

机制链(3 环):

  1. 虚拟线程靠 Continuation 挂起 → 阻塞时把栈帧保存到堆内存,释放 carrier thread
  2. synchronized 持锁期间阻塞 → Monitor owner 关联在 OS 线程上,栈帧无法安全挂起 → carrier thread 陪着等待 → 这就是 pinning
  3. 后果:M:N 调度退化 → carrier thread 无法卸载到其他虚拟线程 → 虚拟线程并发优势消失

证据锚点(去重 ≥10):

对比与权衡:

锁类型uncontended 阻塞竞争时阻塞其他阻塞pin 程度
synchronizedpinpinpin高
ReentrantLock不 pinpin不 pin中
tryLock(timeout)不 pin不 pin(超时放弃)不 pin低
CAS / AtomicReference不 pin不 pin不 pin无
JNI native——pin高

常见误区:

  1. 错误:"synchronized 在虚拟线程下完全不能用" → 正确:JDK 21 官方立场是"尽量用,只是别在其中阻塞",纯计算不 pin → 为什么:pin 发生在阻塞时,不是持有锁时
  2. 错误:"synchronized 会让虚拟线程退化成 OS 线程 1:1" → 正确:不是退化到 1:1,而是 carrier thread 被固定无法卸载其他虚拟线程 → 为什么:M:N 退化的准确描述是"carrier 利用率下降"
  3. 错误:"ReentrantLock 完全不会 pin" → 正确:uncontained 时不 pin,真竞争(park)时也 pin → 为什么:ReentrantLock 只是概率更低,不是零
  4. 错误:"虚拟线程的 pinning 只跟 synchronized 有关" → 正确:JNI / native 方法也会 pin,与锁无关 → 为什么:native 调用时 JVM 无法调度虚拟线程
  5. 错误:"只要用 ReentrantLock 就万事大吉" → 正确:高竞争场景 ReentrantLock 也会 pin,彻底解决要用 CAS / 无锁 → 为什么:并发原语的选择要看竞争程度

适用边界:

延伸追问(≥3)

  1. ReentrantLock 竞争时也会 pin,那怎么完全避免 pin?
  1. JDK 22/24 是否修复了 pinning?
  1. 生产环境怎么判断有没有 pinning 问题?
  1. 虚拟线程下 synchronized 到底能不能用?

面试者参考回答(400–700 字)

【结论】 不是"synchronized 不能用",而是"别在 synchronized 块里阻塞"——阻塞时 carrier thread 被 pin 无法卸载。

【三点】

第一,为什么 pin:虚拟线程靠 Continuation 挂起——阻塞时把栈帧保存到堆内存,释放 carrier thread 干别的。但 synchronized 块持锁期间如果阻塞(IO、sleep、join),JVM 不能挂起这个栈帧,因为锁的 Monitor owner 关联在 OS 线程上,一旦切换走再回来可能破坏锁的一致性。所以 carrier thread 只能陪着这个虚拟线程一起等——这就是 pinning。

第二,准确的说法:不是"synchronized 不能用",而是"别在 synchronized 块里阻塞"。如果 synchronized 块内是纯计算、快速方法,其实不 pin,JDK 21 官方立场也是"尽量用,只是别在其中阻塞"。

第三,替代方案:首选 ReentrantLock——uncontained 时完全不 pin,只有真正竞争(park)时才可能 pin,pin 概率远低于 synchronized。更彻底用 CAS + AtomicReference 无锁化,或 tryLock 非阻塞模式(超时直接放弃,不 pin)。注意:JNI / native 方法也会 pin,与锁无关。排查靠 -Djdk.tracePinnedThreads=full 或 JFR 的 jdk.VirtualThreadPinned 事件。

【边界】 两点提醒:一是 ReentrantLock 也不是零 pin,高竞争下还是会有;二是"完全无锁"要付出正确性代价,生产要根据业务权衡选择——语义正确性永远优先于 pinning 规避。


作答历史

日期评分备注
2026-10-26⭐⭐⭐☆☆首次作答