恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

synchronized轻量级锁会自旋吗?锁升级与适应性自旋深度解析

  • 首页
  • 资讯中心
  • /
  • synchronized轻量级锁会自旋吗?锁升级与适应性自旋深度解析

相关资讯

Cline 3.2 发布:Plan Act 双模式实战,把 Claude 3.5 改到 TaoToken 跑通架构设计 2026/10/12 14:39:50
微网多时间尺度协调调度:电动汽车灵活性如何应对预测误差 2026/10/12 14:39:50
MCP逆向工程实战:解析JSON-RPC握手与工具调用 2026/10/12 14:39:50

最新资讯

YOLOv8+MMAction2两段式行人动作检测:从空间定位到时序分类的工程实践
2.skill实战:用AI技能包构建自动周报生成器
Python+TuShare构建A股自动选股程序:数据、规则与避坑指南
数学建模种植策略可视化:从决策变量到论文级图表
PL/SQL Developer 6.0.0.840汉化版:部署与避坑指南
字节新宠「Trae」对标 Cursor?TaoToken 统一 Key 打通 AI 编程工具链

今日推荐

Debian新手入门:从部署到日常操作的完整指南
MongoDB复制集扩缩容实战:从rs.add到选主事故复盘
条形码目标检测数据集实战:从YOLOv8训练到部署

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

synchronized轻量级锁会自旋吗?锁升级与适应性自旋深度解析

发布时间:2026/10/12 14:39:50
synchronized轻量级锁会自旋吗?锁升级与适应性自旋深度解析 1. 一个看似简单的面试题为什么能问倒一片人前两天在技术群里有人甩了一个问题过来“Java 中的 synchronized 轻量级锁是否会进行自旋”群里立刻分成了两派。一派背书式地回复“会自旋自旋失败后膨胀为重量级锁”另一派则搬出“轻量级锁其实没有自旋循环竞争会直接膨胀”的说法。两边谁也说服不了谁最后索性开始贴源码链接。这其实是一个非常典型的“半懂不懂”问题。答“会”的人大概率是看过几篇锁升级的文章记住了触发顺序答“不会”的人可能啃过一部分 HotSpot 源码但没把上下文串起来。真正能回答好这个问题需要同时理解三件事Mark Word 是什么、锁升级到底怎么发生、自旋代码究竟写在哪个阶段。缺一块结论就容易跑偏。所以这篇文章我想从头把这根线理顺。我不会只给一个“会”或“不会”的结论而是把 HotSpot 里 synchronized 的加锁路径、轻量级锁的 CAS 流程、膨胀成 ObjectMonitor 之后的自旋逻辑以及 JDK 版本差异都展开讲一遍。无论你是准备面试还是想搞清楚锁调优该怎么下手都应该能从中拿到能直接用的结论。先剧透一下核心观点**在主流 HotSpot 实现里轻量级锁这个状态本身没有专门的自旋循环真正发生的自旋是在轻量级锁竞争触发膨胀之后进入重量级锁 ObjectMonitor 的路径上。**但很多面试八股文讲的“轻量级锁自旋失败才升级”是把这个过程简化包装之后的说法。严格说来这种表述是不准确的不过面试时怎么回答才能既不抬杠又显得有深度我会在第 4 节给你一套完整话术。2. 先拆锁升级链路轻量级锁在 HotSpot 里究竟怎么加锁2.1 Mark Word 就是锁状态的“身份证”Java 对象的对象头里有一块区域叫 Mark WordHotSpot 用它同时承担哈希码、GC 分代年龄、偏斜锁线程 ID、轻量级锁 Lock Record 指针、重量级锁 ObjectMonitor 指针等多项职责通过末尾两位锁标志位来区分当前状态。可以简单理解成一张纸条纸条上现在写的是“无锁 哈希码”还是一行“指向线程栈中 Lock Record 的地址”又或者是一行“指向 ObjectMonitor 的地址”决定了这把锁接下来的行为。锁状态与 Mark Word 的大致对应关系如下锁状态Mark Word 内容示意获取方式适用场景无锁哈希码 age 01不需要加锁步骤无竞争偏向锁线程ID epoch 01只在 Mark Word 记录线程ID单线程反复访问同一对象轻量级锁指向栈中 Lock Record 00栈帧中 CAS 夺取锁多线程交替访问、竞争不激烈重量级锁指向 ObjectMonitor 10进入 monitor 的 enter 流程高竞争、多线程同时争抢这张表的最后一行很关键。很多人以为“重量级锁”是某个锁对象直接变了实际上它是指 Mark Word 指向了一个 JVM 内部的数据结构 ObjectMonitor之后线程的阻塞、唤醒、自旋都围绕这个 monitor 展开。2.2 轻量级锁的加锁三步Lock Record 才是主角轻量级锁的加锁过程核心不是“拿一把锁出来”而是把锁信息塞进当前线程栈帧里一个叫 Lock Record 的结构。HotSpot 的 BasicObjectLock / BasicLock 就是这个结构的底层实现。大致过程是这样的当前线程在自己的栈帧里分配一个 Lock Record里面留出一个 displaced header 的槽位。把对象当前的 Mark Word比如无锁状态下的哈希码和 age保存到这个 displaced header 里相当于给现场留个底。执行 CAS 操作尝试把对象 Mark Word 从“无锁状态”改为“指向当前线程 Lock Record 的地址”同时把锁标志位改为 00。如果 CAS 成功说明轻量级锁已经拿到手锁对象还是那个对象只是 Mark Word 指向了线程内部结构。解锁时再做一次反向 CAS把 displaced header 里的内容写回 Mark Word锁就释放了恢复成无锁状态。如果 CAS 失败则分两种情况。第一种是当前线程已经持有锁比如同一个线程里 synchronized 重入那么 Lock Record 里的 displaced header 会被标记为空说明是可重入不需要再抢一次。第二种是确实有其他线程正在持有锁这时轻量级锁这条路已经走不通JVM 会调用 inflate 对它进行膨胀试图升级成重量级锁。这个流程里值得注意的一点是**在 HotSpot 的 slow_enter 路径中轻量级锁的 CAS 失败后并没有一个“先原地重试 N 次”的循环。**它要么成功要么因为重入而完成要么直接进入锁膨胀流程。很多文章里画的“自旋重试”在这里并不存在。如果你拿源码读会看到一个简化后的流程长这样// 伪代码用于理解执行顺序不是 HotSpot 源码 if (mark.isNeutral()) { lockRecord.setDisplacedHeader(mark); if (obj.casSetMark(lockRecordAddress, mark)) { return; // 轻量级锁加锁成功 } } if (mark.hasLockRecord() mark.locker() currentThread) { lockRecord.setDisplacedHeader(null); // 重入 return; } // 来到这里说明有真实竞争 ObjectMonitor* monitor inflate(obj, mark); monitor-enter(currentThread); // 自旋和阻塞都发生在 monitor 里这段伪逻辑解释了为什么我说轻量级锁状态本身没有自旋。你看 inflate 之后紧接着就进入了 monitor 的 enter后续的争夺、自旋、阻塞全部发生在 ObjectMonitor 这一层。2.3 为什么设计轻量级锁而不是遇事就上 monitor理解完机制你可能会问既然竞争会膨胀到重量级锁那轻量级锁的存在意义是什么答案是开销。轻量级锁全程只用用户态的 CAS 和栈帧操作不涉及内核态不需要创建额外的同步对象。只要锁竞争不激烈这个路线远比直接丢给 ObjectMonitor 快。偏向锁更进一步它连 CAS 都省了直接比对线程 ID专门给单线程重复访问的场景做优化。轻量级锁适合“多个线程交替进入临界区”比如两个线程各自在不同时间访问同一个共享对象每次进来时锁早就释放了线程只需要 CAS 一下就能成功。这时候如果直接上重量级锁就会白白付出 monitor 分配、系统调用、上下文切换的代价属于杀鸡用牛刀。但轻量级锁也怕另一个极端两个线程同时卡在临界区入口疯狂竞争。CAS 失败率一高轻量级锁的优劣势就反转了JVM 才会选择膨胀成重量级锁让实际的等待逻辑接管。所以锁升级本质上是在不同竞争烈度下选择成本最低的方案。另外要提醒一点平时说的“锁升级只升不降”严格讲并不绝对。偏向锁在触发批量撤销时可能会恢复到无锁状态轻量级锁无竞争时解锁也能回到无锁。真正不可逆的是膨胀之后的重量级锁一个对象一旦膨胀成 monitor通常就不会再缩回轻量级锁了。面试时如果被问到“锁可以降级吗”能分开讲清楚这两种情况会有加分。3. 自旋的真正落点ObjectMonitor 的进入流程与适应性自旋3.1 膨胀到底做了什么锁膨胀 inflate 做的事是把一个对象从“轻量级锁/无锁”状态变成“Mark Word 指向 ObjectMonitor”的状态。ObjectMonitor 内部维护了 owner 字段、递归计数、EntryList 等待队列、WaitSet 等结构参与竞争和等待的线程信息都记录在这里。为什么 CAS 失败时不直接让线程阻塞而是要经过膨胀因为 JVM 需要一个更强大的数据结构来兜底。轻量级锁只有 Lock Record 和 Mark Word 之间的 CAS 关系处理不了“一堆线程排队等锁、锁超时、线程 wait/notify、可重入计数”这些需求。这些能力都在 ObjectMonitor 里。从对象头的角度看膨胀前后最明显的差异就是 Mark Word 最后两位轻量级锁是 00重量级锁是 10。这也是第 5 节用 jol 做实验时判断锁状态的重要依据。3.2 monitor enter 路径上的自旋循环ObjectMonitor 的 enter 流程才是 synchronized 自旋真正发生的地方。整个过程可以概括为三段先尝试快速获取通过 CAS 尝试把 monitor 的 owner 从 null 设成当前线程。运气好一次就拿到了不需要进入后续流程。适应性自旋如果快速获取失败线程不会马上 park 去睡觉而是进入一段自旋循环。循环里反复检查 owner 是否变化只要发现 owner 被释放就再次尝试抢夺 owner。阻塞等待自旋达到一定条件还没成功线程才会把当前节点加入 EntryList然后通过 park 机制挂起等待被持有锁的线程唤醒。第二步就是大家常说的 synchronized 自旋。它的核心思想是线程刚把锁让出来唤醒和阻塞的代价可能比短暂的自旋更大尤其是临界区很小、持锁时间极短的时候。与其立刻切换线程上下文不如先在 CPU 上排队等一会儿说不定锁马上就释放了。这里必须再次强调位置关系自旋循环发生在膨胀后的 ObjectMonitor::enter 里不是在轻量级锁阶段的 Mark Word CAS 里。轻量级锁所谓的“自旋”实际上是“竞争触发膨胀 monitor 进入时自旋”这个组合效果。很多教程为了流程好记直接说成“轻量级锁自旋失败后膨胀成重量级锁”造成了概念混淆。3.3 适应性自旋不是死循环撞运气JDK 6 之前HotSpot 的自旋更像是一种固定次数的重试可以用-XX:PreBlockSpin指定一个固定迭代数。但这种方式太僵硬锁竞争激烈的系统调小了没效果调大了又白烧 CPU。JDK 6 之后引入的适应性自旋 Adaptive Spinning 改变了思路。JVM 会根据同一个锁之前自旋成功的历史记录动态调整自旋时长逻辑上很像“信任评分”自旋成功率高的锁下次多转几圈成功率低的锁下次少转几圈甚至直接跳过自旋进入等待。这套机制由 ObjectMonitor 内部维护的自旋统计参数驱动不同对象能表现出完全不同的自旋节奏。实际调优时不太建议普通应用去碰-XX:PreBlockSpin。它是老版本留下的参数在新版本里还保留着兼容入口但生效与否、默认值多少不同 JDK 小版本都可能不一样。真想确认你当前环境的情况最可靠的办法是执行java -XX:PrintFlagsFinal -version | grep -i PreBlockSpin\|UseAdaptiveSpinning在自己手头的 JDK 上跑一下比背参数表靠谱得多。那么回到标题本身怎么用一句话精确定义答案我会这样说如果问 HotSpot 中的轻量级锁状态本身是否会自旋答案是不会。轻量级锁的 CAS 失败会直接走向膨胀自旋发生在膨胀后的 ObjectMonitor 进入流程中并且是适应性自旋。这句话既回答了“会不会”又把自旋的位置说清楚了后面无论面试官怎么深挖你都有东西可讲。4. 面试答案怎么写标准话术与四个高频误区4.1 一段经得起追问的面试答案面试时最怕的不是观点错而是只背结论、经不起追问。关于这道题我建议你在心里准备一段“分层答案”先给结论在 HotSpot 里synchronized 的锁升级路径是无锁、偏向锁、轻量级锁、重量级锁四个状态。说明轻量级锁的加锁本质通过栈帧中的 Lock Record 和对象 Mark Word 之间的 CAS 完成的竞争触发膨胀。指出自旋位置膨胀成重量级锁后ObjectMonitor 的 enter 过程会先尝试 CASCAS 失败后进行适应性自旋自旋失败才 park。补充版本和参数JDK 6 以后默认使用适应性自旋具体用-XX:PrintFlagsFinal可在当前 JDK 上确认。如果有余力再补一句 JDK 15 之后偏向锁默认被关闭JEP 374锁路径和旧版本有差异。这段话的好处是每一层都对应着一个可以继续深挖的问题。面试官问 Mark Word 结构你能答问自旋实现你能答问锁升级路径你能答问参数调优你也能答。4.2 误区一轻量级锁自旋失败后才膨胀这是最广泛流传的说法也是很多人答错这题的根源。准确讲轻量级锁的 CAS 失败会直接导致锁膨胀膨胀之后 monitor 才开始自旋。所以不是“先在轻量级锁上自旋若干次再考虑升级”而是“CAS 失败后马上走膨胀流程自旋是膨胀后的事情”。为什么这个误区流传得这么广一方面是因为早期很多锁升级的科普文章为了简化把 monitor 进入流程里的自旋也算成“轻量级锁阶段”的优化另一方面是因为 JVM 的设计目标本来就是隐藏内部路径从外部看你确实会观察到线程在进入临界区前短暂等待然后成功至于是薄锁还是 monitor 自旋带来的成功肉眼根本分辨不出来。4.3 误区二自旋次数是固定写死的网上经常能看到“自旋次数默认 10 次”之类的说法。这种说法片面了。如果你在 JDK 8 上看-XX:PreBlockSpin的默认值确实能看到类似 10 的数字但那是老模型里固定自旋的入口。JDK 6 之后大部分场景走的是适应性自旋次数会随监控数据动态改变。更关键的是真实的自旋循环不一定按“转动一次”计数而是会结合 owner 释放事件、自旋失败比例、当前 CPU 核数等信息动态调整。你要是按固定系数去调PreBlockSpin在新版本里很可能会发现没什么效果。4.4 误区三synchronized 的锁竞争一定产生阻塞还有不少同学认为只要出现锁竞争线程状态就应该是 BLOCKED配合 jstack 能立刻抓到。实际上如果锁竞争发生在 monitor 的自旋阶段线程还在 CPU 上空转jstack 显示的很可能是 RUNNABLE直到真正 park 挂起后才变成 BLOCKED 或 WAITING。所以在排查线上问题时会有一个陷阱看到大量线程 RUNNABLE不一定是 CPU 在执行业务代码也可能是正卡在锁自旋里。要区分这两者光靠 jstack 一两张快照往往不够还得结合 JFR、async-profiler 或者更长时间地采样分析原生栈才能定位到 synchronized 的自旋热区。4.5 误区四只要用 synchronized 就是重量级锁这个误区在我早期带团队时特别常见。很多人看到 synchronized 就把它和“重锁、慢、阻塞”划等号忽略了 JVM 的锁升级机制。实际上一个锁对象在没有竞争的时候可能连偏向锁都不需要单线程反复进入可能一直停在偏向锁或轻量级锁状态开销非常低。如果把锁状态和锁是否发生系统调用混为一谈就很难理解为什么现代 JVM 上 synchronized 的性能并不是“无脑差”。锁膨胀、自旋、park/unpark 这一整套机制最终目的都是让锁在不同竞争烈度下自适应成本。下面这张表把常见说法和源码视角的事实放在一起方便对照记忆常见说法源码层事实轻量级锁会自旋失败后升级轻量级锁 CAS 失败直接膨胀自旋在 monitor enter 里自旋次数固定默认是适应性自旋动态调整竞争就阻塞线程自旋阶段线程仍在运行park 后才阻塞synchronized 都是重量级锁无竞争时可能是偏向锁或轻量级锁5. 亲手验证一次用 jol 观察对象头与锁膨胀5.1 为什么推荐用 jol 而不是瞎猜说了这么多原理肯定有人想知道怎么亲眼看一看锁状态的切换。这里我推荐用 OpenJDK 官方出品的 jolJava Object Layout工具。它能直接打印对象头布局包括 Mark Word 的内容和锁标志位是观察锁状态最直观的手段之一。在 Maven 项目里加一个依赖dependency groupIdorg.openjdk.jol/groupId artifactIdjol-core/artifactId version0.17/version /dependency然后写一个最简单的实验程序。5.2 实验一单线程持锁时的对象头变化import org.openjdk.jol.info.ClassLayout; public class LockHeaderDemo { public static void main(String[] args) throws InterruptedException { Object lock new Object(); System.out.println(初始无锁); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); synchronized (lock) { System.out.println(单线程持锁); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } System.out.println(解锁后); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } }在 JDK 8 默认开启偏向锁的情况下第一次 synchronized 可能直接进入偏向锁锁标志位显示 01但 Mark Word 里不再是普通哈希码而是记录了当前线程 ID。如果你希望跳过偏向锁这一步只看轻量级锁可以在启动参数里加上-XX:-UseBiasedLocking这样单线程持锁时你会看到 Mark Word 的锁标志位变成 00整个 Mark Word 指向的是某个栈地址这就是 Lock Record 的地址。解锁后再打印锁标志位会回到 01哈希码重新出现。这里要提醒一句不同 JDK 版本打印出的 Mark Word 数值格式有差异JDK 8、JDK 11、JDK 17 之间的输出看起来可能完全不一样。不要死盯某一个数值重点看锁标志位和对象头描述格式。5.3 实验二多线程高竞争下膨胀到 monitor再看一个更接近标题问题的实验多个线程同时抢同一把锁制造高竞争然后再看对象头。import org.openjdk.jol.info.ClassLayout; public class MonitorInflateDemo { public static void main(String[] args) throws InterruptedException { Object lock new Object(); for (int i 0; i 8; i) { new Thread(() - { long end System.currentTimeMillis() 500; while (System.currentTimeMillis() end) { synchronized (lock) { // 模拟短暂临界区 } } }).start(); } Thread.sleep(1000); System.out.println(高竞争后对象头); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } }在高竞争跑完之后锁对象的 Mark Word 大概率会变成锁标志位 10同时 Mark Word 里出现一个指向 ObjectMonitor 的地址。这正好对应我们前面讲的流程轻量级锁 CAS 竞争到一定烈度后JVM 把它膨胀成了重量级锁后续线程全部走 monitor 的 enter 路径。如果你想在这个基础上继续观察自旋可以配合 JFR 的线程阻塞事件或者用 async-profiler 看看锁对象的 native 栈会发现一段正处于自旋阶段的线程状态它还没有 park但栈顶已经被 ObjectMonitor 相关的 native 方法占据。这一步能得到比 jstack 更准确的证据。5.4 用 PrintFlagsFinal 确认当前 JDK 的自旋参数每个跑实验的人JDK 版本都可能不同所以我在团队里一直强调“先确认环境再下结论”。最省事的方法就是java -XX:PrintFlagsFinal -version然后过滤出锁相关的参数比如UseBiasedLocking、PreBlockSpin、UseHeavyMonitors等等。同一个参数在 JDK 8 和 JDK 17 里的默认值甚至语义可能都不一样。很多网上讨论吵得不可开交本质上就是 JDK 版本不一致导致的。6. 锁竞争调优的边界知道自旋在哪之后再谈怎么优化6.1 自旋不是“越少越好”也不是“越多越好”理解了自旋发生在哪一环最容易产生的冲动就是去调参。我自己早期也干过这种事把一个压测系统的-XX:PreBlockSpin从默认值反复调大调小最后发现效果不是线性的。自旋时间太短线程频繁走 park/unpark上下文切换成本上升自旋时间太长CPU 被无意义占住真正干活的线程可能调度不过来。适应性自旋的价值就在于它把“调参”这件事交给 JVM 运行时统计一般场景下没必要手动干预。如果你非要在高并发场景下调别只看自旋次数先去看锁竞争率、临界区大小、线程上下文切换频率这些指标。多数情况下把临界区缩短、把锁粒度拆细效果远好过调某几个 JVM 参数。6.2 逃逸分析和锁消除也会影响自旋还有一个经常被忽略的因素JIT 的逃逸分析。如果锁对象根本没有逃逸出方法JVM 可能会直接消除 synchronized锁都不加自然也就谈不上自旋和膨胀。这就是为什么本地写几行 synchronized性能表现和线上高并发场景完全不同。知道这一点对面试也有帮助。别人问“锁升级这么复杂那 JVM 会不会自己优化掉”你可以顺势把逃逸分析、锁消除、锁粗化这几个概念接上。要注意的是JIT 优化受到代码形态和运行时采样影响结果不是稳定可预测的所以“用 jol 观察到锁被消除了”这种实验并不能覆盖所有并发场景。6.3 我个人的一点实操体会这个题目我见过太多次了也和很多不同背景的工程师对过线。给我的直观感受是能在面试里把这道题答得漂亮的人往往不是记住了“会”或“不会”而是能把 Mark Word、Lock Record、ObjectMonitor、适应性自旋这几个节点串成一条完整的链路。链路上的任意一环都可以展开继续问而这个展开能力才是壁垒。最后分享一个我在地时比较常用的验证思路如果你在一个高吞吐场景里怀疑 synchronized 成了瓶颈不要急着换并发工具先抓一把线程快照确认绝大多数线程是饿在锁等待里还是随时能进临界区。如果是后者锁本身未必有锅问题可能出在临界区太长。搞明白自旋在哪其实是在帮你建立判断锁性能问题的坐标系坐标系对了后面怎么调都不太会跑偏。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号