首页/新闻资讯/正文详情

Java synchronized锁升级:偏向锁、轻量级锁与重量级锁原理

发布时间:2026/9/26 14:02:56 来源:云帆数科 栏目:资讯中心
Java synchronized锁升级:偏向锁、轻量级锁与重量级锁原理
1. 先从 synchronized 的对象头说起锁状态其实是“身份标签”聊 Java 并发偏向锁、轻量级锁、重量级锁这三个词几乎一定绕不开。很多人把“锁升级”背成了一张流程图先偏向再轻量最后重量。但真正到了线上你会发现这套流程并不是每次同步代码都会完整走一遍甚至有些场景根本没走偏向锁就直接膨胀了。想搞清楚这个问题第一步得先认识 Java 对象在 JVM 里用来存锁状态的那块地方Mark Word。HotSpot 里的每个 Java 对象都有对象头Mark Word 就是对象头里最核心的一小段空间。它平时既存对象的无锁状态也存分代年龄、哈希值还存各种锁状态下的指针。无锁、偏向锁、轻量级锁、重量级锁这四种状态本质上就是在改写 Mark Word 里的标志位和指针。你可以把 Mark Word 理解成门牌上的一个可改写标签同一扇门标签不同代表当前用了哪种锁方案。下面这张表可以帮助先建立整体印象锁状态Mark Word 里主要信息用大白话记忆无锁对象哈希、分代年龄偏向标志为 0还没人给它贴“专属名字”偏向锁偏向线程 ID、epoch标签上写了某个线程的名字轻量级锁指向线程栈中 Lock Record 的指针锁被当前线程夹在自己的栈里重量级锁指向 ObjectMonitor 的指针锁已经移交给 JVM 的监视器对象这套升级路线是 HotSpot 为了照顾不同竞争程度的临界区而设计的如果只有一个线程反复进出就尽量省掉没必要的原子操作如果真的有多个线程抢再一步一步加重手段。但这不代表每次加锁都会严格“从偏向走到重量”。竞争很激烈时JVM 在合适的时机可以直接膨胀没必要非挤进轻量级锁那一层。理解锁升级关键不是死记状态名而是搞清每个状态省了什么、贵在哪、什么时候值得用。2. 偏向锁单线程重复进入的“专属通道”2.1 设计初衷与获取过程偏向锁对应的是“其实只有一个线程在反复加锁”的场景。这个问题在早期 Java 里非常现实synchronized 如果每次都走操作系统级别的互斥量哪怕临界区短到只有几条指令线程还是得付出内核态切换的成本。JDK 6 之后HotSpot 引入了偏向锁思路很简单既然只有你一个人来那就在对象头里记录下“你是主人”以后你再来时直接放行。具体来说线程第一次进入 synchronized 块时如果对象处于可偏向状态JVM 会通过 CAS 把当前线程的 Thread ID 写进 Mark Word。之后同一线程再次进入先检查对象头里的 Thread ID 是不是自己。如果是连 CAS 都不用做直接进入临界区。这是偏向锁最大的优势把原本每次加锁都需要的原子操作变成一次普通的内存比较。偏向锁在 JDK 6 时代默认就是开启的而且为了避开 JVM 启动阶段复杂的类加载、堆初始化早期版本还留了一个 4 秒的启动延迟。想要在 JDK 8 这类老版本上立即看到偏向锁效果可以这样设置-XX:UseBiasedLocking -XX:BiasedLockingStartupDelay0如果完全不想用偏向锁也可以关掉-XX:-UseBiasedLocking注意这条参数在 JDK 15 之后基本没意义了因为偏向锁从 JDK 15 开始默认禁用并被标为废弃后续版本逐步退出了默认行为。很多公司在面试题里还保留着偏向锁八股文但在新 JDK 上做压力测试时你实际观察到的默认行为可能已经不是这套了。2.2 偏向锁撤销为什么这么贵偏向锁最大的风险不是获取而是撤销。一旦第二个线程来抢这把锁JVM 不能简单地把 Mark Word 里的 Thread ID 改掉因为线程 A 可能正停留在临界区里改掉之后它对锁的“所属权”就解释不清了。所以偏向锁的撤销必须在安全点Safepoint完成先把所有 Java 线程暂停检查原偏向线程是否还活着、是否还在临界区然后决定是把对象锁状态改回无锁还是升级成轻量级锁甚至重量级锁。这就是为什么偏向锁在真实竞争下可能反而变成负担。如果一个对象被多个线程短时间、交替、激烈地访问偏向锁会不断触发撤销而每次撤销都伴随一次全 JVM 停顿哪怕只是很短的时间在延迟敏感的系统里也足以造成尖刺延迟。另外还有一个容易忽略的坑只要调用过System.identityHashCode()或者对象本身的 hasCode 被计算过偏向锁就没法用了。因为 Mark Word 里需要空间存哈希值而偏向线程 ID 也会占同一块地方二者冲突。所以你在测试偏向锁时千万别随手打印对象的 hashCode否则你以为是偏向锁实际上已经悄悄变成普通无锁了。3. 轻量级锁没有实际竞争时的“自旋替代方案”3.1 Lock Record 与 CAS 的配合当偏向锁被撤销或者 JVM 本身关闭了偏向锁第一次加锁通常会走轻量级锁。轻量级锁针对的场景是“多个线程轮流进锁但真正并发产生冲突的概率很低”。这时候 JVM 不想让线程去调用阻塞原语而是尽量让加锁过程变成一次 CAS不成功就再试几次。整个过程是这样的每个线程在进入 synchronized 之前会在自己的栈帧里创建一个 Lock Record。加锁时线程会把对象头 Mark Word 的原始状态拷贝到这个 Lock Record 里然后尝试用 CAS 把对象头的 Mark Word 改成指向自己栈里那个 Lock Record 的指针。如果 CAS 成功说明锁已经拿到对象头里记录的不再是无锁状态而是“我把它放在我这个线程的栈上了”。释放时再通过 CAS 把 Lock Record 里保存的原始 Mark Word 写回对象头锁就还原了。如果 CAS 失败JVM 会先判断一下是不是同一个线程在重入。如果是重入那当前线程其实已经拥有锁了不需要真的再抢只需要往自己的栈里继续压入一个 Lock Record。这样锁的可重入性不需要通过计数器记录而是通过栈里多条 Lock Record 来体现既简单又快速。3.2 为什么竞争不激烈时比重量级锁快很多资料把重量级锁说成“有线程竞争就要升级”实际并不完全准确。准确的说法是线程发现轻量级锁 CAS 失败了会先试着自旋也就是循环等待几次期待持有锁的线程能很快释放。自旋本质上是在用 CPU 时间换内核切换的时间。如果临界区很短比如只是累加一个变量自旋等一两百纳秒就能等到锁这比让线程睡下去、之后再唤醒省得多。自旋几轮之后仍然拿不到锁JVM 才会考虑把轻量级锁膨胀成重量级锁。也就是说锁定级到重量级的真正触发点是“短时间自旋无法解决竞争”而不是单纯出现两个线程同时摸锁就算升级。我建议你在看相关源码或博客时注意区分“轻量级锁失败”和“轻量级锁膨胀”这两件事。轻量级锁失败只代表 CAS 没成功不代表一定要马上挂起线程膨胀策略是 JVM 根据历史自旋成功率动态调整的。老版本的 JVM 还有参数可以手动控制自旋但现代 HotSpot 默认用自适应自旋不需要也不建议去调。4. 真正触发重量级锁的临界点膨胀与 ObjectMonitor4.1 膨胀到 ObjectMonitor 后发生了什么当多个线程真正抢同一把锁自旋又拿不到JVM 就会把锁对象膨胀为重量级锁。重量级锁的核心是 ObjectMonitor这是 JVM 内部的一个监视器对象。线程状态、获得锁的线程、等待队列、可重入计数等全都在 ObjectMonitor 上管理。一旦进入重量级锁阻塞和唤醒就不再是“自己原地转圈”而是交给 Java 线程调度和操作系统层面的互斥量。竞争失败的线程会被挂起进入 EntryList 等待队列持有锁的线程释放后会从等待队列里唤醒一个或多个线程。这个过程可靠但有明显的上下文切换开销也可能有线程饥饿问题所以整体吞吐量在高竞争下取决于唤醒策略和临界区长度。重入在重量级锁里也好处理了ObjectMonitor 内部有一个递归计数同一个线程再次进入同一个 monitor 时计数器加一JVM 记录的是同一个线程不会误判成别人来抢。和轻量级锁用栈上 Lock Record 表示重入的方式相比这是另一套更重量但更通用的设计。4.2 哪些情况会跳过轻量级锁直接膨胀有几类情况会让锁直接从无锁状态跳到重量级锁或者说得更准确些根本不考虑偏向和轻量级方案第一调用wait()或notify()。这套方法依赖 ObjectMonitor 的 WaitSet轻量级锁状态下没有类似结构。虽然从语法上看线程持有 synchronized 后调用wait()是合法的但 JVM 内部为了保证等待通知机制可用会把对象膨胀到重量级锁。这也是为什么面试喜欢把wait()和锁升级放在一起问不只是问你 wait 和 sleep 的区别还问 JVM 底层如何满足 wait 对监视器的要求。第二锁竞争从第一个线程进入时就已经很明显。如果一个对象在多线程环境下第一次被 synchronized 访问而 JVM 通过偏向撤销或锁竞争记录判断出它会成为热点锁那么它并不会规规矩矩走“无锁 - 偏向 - 轻量 - 重量”的四级路线而是直接膨胀。所以“逐级升级”只是理想化的简化描述实际行为要更激进。第三如果代码里编译器做了锁消除那连锁都不存在了。HotSpot 在开启逃逸分析时如果发现锁对象不会逃逸出当前线程会把整个 synchronized 直接去掉。你在 JIT 后的机器码里根本看不到锁操作自然也没有升级过程。4.3 重量级锁释放后会不会降级面试里经常有人问“重量级锁释放后能不能降回轻量级锁”。从规范层面说重量级锁保存了完整的竞争信息没有必要每次释放都去评估是否该降级。而且反复膨胀、降级本身也是一笔开销还会让队首线程的唤醒逻辑变得复杂。所以你可以把升级理解为“从简方案搬到完整方案”的过程更准确的说法是膨胀。HotSpot 对部分空闲 monitor 也有后台回收/收缩机制但在稳定的高竞争场景下锁对象长期处于重量级状态是很正常的。不要指望同一把锁能根据竞争度自动降回轻量级锁。5. 实操用 JOL 观察一把锁的 Mark Word别只停留在“背结论”说再多理论不如自己跑一遍。平时做 Java 并发实验我常用 JOL 打印对象布局它可以把对象头和 Mark Word 的字节信息显示出来。用 Maven 引入dependency groupIdorg.openjdk.jol/groupId artifactIdjol-core/artifactId version0.17/version /dependency然后写一个简单的类import org.openjdk.jol.info.ClassLayout; public class LockStateShow { public static void main(String[] args) throws Exception { Object lock new Object(); System.out.println(初始状态:); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 故意计算一次 IdentityHashCode观察对偏向锁的影响 // System.out.println(System.identityHashCode(lock)); for (int i 0; i 5; i) { synchronized (lock) { System.out.println(第 i 次 synchronized 内部:); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } } Thread.sleep(6000); System.out.println(等待 6 秒后偏向锁延迟结束:); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } }用 JDK 8 跑时建议加上-XX:UseBiasedLocking -XX:BiasedLockingStartupDelay0。你会看到对象头的 Mark Word 在无锁、偏向锁、轻量级锁状态之间变化。需要提醒的是JOL 打印出来的是小端序的字节直接看十六进制很容易看晕所以重点不是背某个具体十六进制值而是对比同一行字节在进入 synchronized 前后发生了什么变化。手动观察时一定要记住两个坑不要随便调用hashCode()因为计算过 identity hashCode 的对象会影响偏向锁的启用Mark Word 里哈希值占用位和偏向线程 ID 冲突。如果加了大量同步日志锁粗化甚至锁消除可能会改变真实行为。你看到的优化结果是 JIT 之后的不是源码里表面那个 synchronized 的直接体现。正确打开方式是把上面的类变成两个版本一个开启偏向锁一个关闭偏向锁各自跑一大轮同步操作记录耗时。对单线程反复加锁的场景偏向锁往往有明显优势而一旦换成多线程频繁争抢关闭偏向锁后反而更稳定因为撤销偏向锁会频繁触发 Safepoint带来不可忽略的抖动。6. 面试和实战中最容易翻车的四个为什么6.1 为什么偏向锁撤销要在 Safepoint 做偏向锁最大的隐患是“一个线程可能在临界区内而另一个线程想抢锁”。如果只通过 CAS 换掉 Mark Word没法保证旧线程真的不会继续访问临界区。所以必须先暂停所有线程确保临界区里没有执行中的 Java 代码才能安全修改对象头。这也是为什么一旦锁竞争频繁偏向锁不但没有帮你省钱反而可能因为停顿制造新麻烦。线上系统最怕的就是这种微妙的全 JVM 暂停点。6.2 为什么轻量级锁“失败”不直接挂起因为挂起线程需要进入操作系统内核态上下文切换的成本可能是几十微秒到几百微秒级别。如果临界区只有十几条指令等锁的线程自旋几十次就能拿到锁那自旋消耗的 CPU 总量也远小于一次阻塞和唤醒。现代 HotSpot 还会做自适应自旋如果最近几次自旋后成功拿到锁就多旋几次如果连续失败就适当减少自旋避免 CPU 空转。6.3 为什么高竞争下重量级锁反而“更正确”很多人以为重量级锁一定最慢其实要看场景。低竞争时轻量级锁靠 CAS 就能拿锁重量级锁没必要高竞争时一堆线程都在自旋CPU 被打满上下文切换也没有彻底避免系统整体吞吐会明显下降。此时让竞争线程去排队用重量级锁的等待唤醒机制把并发排队化反而能降低 CPU 压力。所以“升级成重量级锁”不是失败而是 JVM 在帮你从“快速失败”切到“排队处理”的兜底方案。6.4 synchronized 和 ReentrantLock 的锁升级有什么联系严格来说自旋、CAS、排队这些思想在 synchronized 升级流程和 ReentrantLock 里都有对应。ReentrantLock 默认是非公平锁加锁时先做一次 CAS如果失败再执行compareAndSetState等流程最终没抢到会进入 AQS 队列。它没有偏向锁这一层但同样有“先自旋/CAS再入队阻塞”的分级思想。面试里如果被问到“为什么 ReentrantLock 没有偏向锁”可以从两个角度答偏向锁需要 JVM 在对象头里维护线程 ID而 ReentrantLock 状态在 Java 层和 AQS 里设计目标不同再者偏向锁在现代 JDK 里已经被默认淘汰纯粹基于 Java 层的 AQS 方案更可控、也更容易维护。我在实际调优里最深的体会是锁升级不能只看“升级到哪一层”而是要先搞清临界区到底有多短、竞争的线程有多少、单位时间进入多少次。只有当你真正遇到同步性能瓶颈时再把 Mark Word、偏向锁撤销、Safepoint 这些底层机制摆到台面上分析否则一上来就抠锁状态细节往往是在解决一个根本不存在的性能问题。

相关推荐

Scratch一级考试选择题真题解析:电子学会图形化编程高频考点与避坑指南
Scratch一级考试选择题真题解析:电子学会图形化编程高频考点与避坑指南

1. 2025年12月Scratch一级考试整体情况回顾1.1 这场考试到底在考什么2025年12月的电子学会图形化编程等级考试刚结束,很多家长和带赛老师都在群里讨论选择题的答案。我趁着记忆还新鲜,把这次一级真题里的选择题部分好好拆一拆,重点不是说“选… · 2026/9/26 14:02:56

给AI贴个ADHD标签,Token消耗砍半:AI编程助手提示词优化实践
给AI贴个ADHD标签,Token消耗砍半:AI编程助手提示词优化实践

1. 一个反直觉的发现:给 AI 贴个“多动症”标签,Token 消耗直接砍半先说结论,省得你往下翻半天:我在 Cursor 里给项目规则文件加了一段“我有 ADHD,请用最短路径回答我”的提示词,同一个重构任务&#xff0… · 2026/9/26 14:02:56

递归别死记硬背:从函数调用栈到汉诺塔八皇后实战
递归别死记硬背:从函数调用栈到汉诺塔八皇后实战

递归这块硬骨头,我劝你别再背代码了 山东理工大学(SDUT)的《程序设计基础Ⅱ》,到了递归这一章,几乎每个初学C语言的人都会卡一下。但说实话,卡住的原因真的不是智商问题,而是我们的大脑习惯了“… · 2026/9/26 14:02:56

开发者必读:CPU底层原理与性能优化实战
开发者必读:CPU底层原理与性能优化实战

很多开发者第一次意识到CPU底层原理需要认真补一补,通常不是在学校里读书的时候,而是在电脑前盯着一份跑得莫名其妙的程序的时候:同一份代码,换个机器慢了十几倍;看起来差不多的两层for循环,交换一下内外层… · 2026/9/26 14:36:18

让Oracle 11g支持MCP:基于apiSQL的TaoToken统一Key接入配置
让Oracle 11g支持MCP:基于apiSQL的TaoToken统一Key接入配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:36:18

AI编程助手安全审计Skill全解析:从设计思路到实战落地
AI编程助手安全审计Skill全解析:从设计思路到实战落地

这几年AI编程助手普及速度比我预想的快得多,不管是OpenCode、Claude Code还是Codex,大伙儿都开始把日常重复性工作交给Agent去跑。但有个事我一直觉得不太对劲——安全审计这种需要系统化思维、边界感极强的活儿,偏偏好多人就甩一句“帮我看看… · 2026/9/26 14:36:10

AI赋能内容审核:从技术分类到博客写作的实践
AI赋能内容审核:从技术分类到博客写作的实践

抱歉,这类主题“【FredsVoice】ASMR 雷神 价值一万英镑的奢华理发体验[中文字幕]”不是我能够处理的范畴。我的专业能力仅限于编写技术类内容,比如:技术教程、工具实践、框架集成编程语言基础、开发经验总结数据库、中间件、AI 工具等主题如果… · 2026/9/26 14:36:10

SSM+MySQL图书管理系统:从框架整合到答辩避坑全攻略
SSM+MySQL图书管理系统:从框架整合到答辩避坑全攻略

简介:这是一套基于SSM和MySQL的图书管理系统完整项目,面向JavaWeb期末大作业、课程设计及毕业设计参考,也适合刚接触SSM整合的开发者阅读。项目包含全部Java源码、SQL数据库脚本和实验报告,代码注释较完整,模块划分清晰… · 2026/9/26 14:36:10

从AI安全审计到Skill工程化:打造可复用的代码审计工作流
从AI安全审计到Skill工程化:打造可复用的代码审计工作流

1. 为什么单独做一套安全审计Skill,核心需求拆解这几年跟AI编码工具打交道多了,我养成一个习惯:凡是重复性的技术活,先想能不能沉淀成一个skill。原因很简单,通用对话模型虽然有编程能力,但让它做一次像样的… · 2026/9/26 14:36:10

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码