news 2026/2/16 20:38:46

锁的进化史:从偏向锁到重量级锁的奇幻之旅

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
锁的进化史:从偏向锁到重量级锁的奇幻之旅

文章目录

    • 为什么需要这么多锁?
    • 锁的“状态机”:四种锁状态
    • 偏向锁:专一的锁
      • 为什么需要偏向锁?
      • 偏向锁的工作原理
      • 偏向锁的撤销
    • 轻量级锁:温和的竞争
      • 为什么需要轻量级锁?
      • 轻量级锁的工作原理
      • 自旋优化:耐心等待的策略
    • 重量级锁:真正的强者
      • 重量级锁的实现
      • 重量级锁的工作流程
    • 锁升级的全过程
    • 实战场景分析
      • 场景一:单线程环境(适合偏向锁)
      • 场景二:低竞争环境(适合轻量级锁)
      • 场景三:高竞争环境(需要重量级锁)
    • 锁优化的其他技术
      • 锁粗化(Lock Coarsening)
      • 锁消除(Lock Elimination)
    • 如何选择合适的锁策略?
    • 总结
    • 参考文章

大家好,我是你们的技术老友科威舟,今天,我们要一起探索Synchronized锁的升级之路——从偏向锁、轻量级锁到重量级锁的奇幻之旅。如果你曾对Java并发编程感到头疼,那么这篇文章就是你的布洛芬!

深入理解Java并发编程的锁优化,让你的程序性能飞起来!

为什么需要这么多锁?

在开始之前,我们先思考一个简单的问题:为什么Java不直接用最强大的重量级锁,而是要搞这么多锁状态?

想象一下,你去一家快餐店点餐。

重量级锁就像在收银台前修了一个小房间,每次只能进入一个人点餐,其他人必须在外面排队等待。这安全吗?绝对安全!但效率呢?堪忧!

而现实中,大多数情况是:餐厅里其实没什么顾客(没有竞争),或者即使有多个顾客,也是轮流点餐(交替执行),而不是同时挤在收银台前。

JDK的开发者们也意识到了这个问题,于是在JDK 1.6中,对synchronized进行了大幅优化,引入了我们今天要讲的锁升级机制

锁的“状态机”:四种锁状态

Java中的锁有四种状态,它们的关系如下所示:

锁状态标志位特点
无锁01对象未锁定
偏向锁01优化同一线程重复获取锁的场景
轻量级锁00优化多个线程交替执行同步块的场景
重量级锁10真正的互斥锁,适用于高竞争场景

锁只能从低到高升级,不能降级(虽然有极少数特殊情况,但一般认为不可降级)。

偏向锁:专一的锁

为什么需要偏向锁?

HotSpot的作者发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。比如,你在一个线程安全的多步操作中,可能会多次进入同一个同步块:

publicclassSafeCounter{privateintcount=0;publicvoidsafeIncrement(){synchronized(this){count++;// 其他操作...synchronized(this){// 再次进入同步块count++;}}}}

如果没有偏向锁,每次进入同步块都需要执行CAS操作,而CAS虽然比重量级锁高效,但仍有开销。

偏向锁的工作原理

偏向锁的核心理念是:如果锁始终由同一个线程使用,就不要反复加锁解锁了

加锁过程

  1. 检查对象头的Mark Word,判断是否处于可偏向状态(标志位为01,是否偏向为0)
  2. 如果是可偏向状态,通过CAS操作将当前线程ID记录到Mark Word中
  3. 如果CAS成功,该线程以后每次进入这个同步块,都不需要任何同步操作

举个例子:偏向锁就像是你家的门锁。只有你家人(同一线程)有钥匙,每次回家直接开门就行,不需要每次都在门口检查身份证。

偏向锁的撤销

当有另一个线程尝试获取偏向锁时,偏向锁就要被撤销了。这个过程需要等到全局安全点(在这个时间点上没有正在执行的字节码),然后检查原持有偏向锁的线程是否还存活。

  • 如果原线程已不存活或不在同步块中:将对象设置为无锁状态,然后新线程可以重新偏向或升级为轻量级锁
  • 如果原线程还在同步块中:升级为轻量级锁

偏向锁的适用场景:只有一个线程访问同步块,且不存在竞争的情况。在高并发场景下,偏向锁反而会降低性能(因为多了撤销操作),此时可以通过-XX:-UseBiasedLocking禁用。

轻量级锁:温和的竞争

为什么需要轻量级锁?

当偏向锁遇到竞争时,就会升级为轻量级锁。轻量级锁适应的场景是线程交替执行同步块,而不是真正的同时竞争。

想象一下公司卫生间的使用情况:多个人会使用,但通常是轮流使用,而不是同时挤在门口争夺使用权。

轻量级锁的工作原理

加锁过程

  1. 在代码进入同步块时,如果同步对象处于无锁状态,JVM会在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间
  2. 将对象头的Mark Word复制到锁记录中(称为Displaced Mark Word)
  3. 使用CAS操作尝试将对象的Mark Word更新为指向锁记录的指针
  4. 如果CAS成功,当前线程获得锁;如果失败,表示存在竞争,尝试自旋获取锁

轻量级锁的释放

  1. 使用CAS操作将Displaced Mark Word替换回对象头
  2. 如果成功,同步完成;如果失败,表示锁已膨胀,需要在释放锁的同时唤醒被挂起的线程

自旋优化:耐心等待的策略

轻量级锁在竞争失败后,不会立即升级为重量级锁,而是会进行自旋等待

自旋可以理解为:“我再等一会儿,说不定马上就能拿到锁了”。

// 自旋的简单理解for(inti=0;i<MAX_SPIN_TIMES;i++){if(tryGetLock()){// 获取锁成功!return;}// 稍微等待一下再尝试shortWait();}// 自旋多次还没拿到锁,升级为重量级锁upgradeToHeavyweightLock();

JDK 1.6引入了适应性自旋,意味着自旋时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态决定。

重量级锁:真正的强者

当轻量级锁自旋超过一定次数(或一个线程持有锁,另一个在自旋,又有第三个来访时),轻量级锁会升级为重量级锁。

重量级锁的实现

重量级锁依赖于操作系统的mutex锁实现,线程的阻塞和唤醒需要从用户态切换到内核态,成本很高。

重量级锁使用ObjectMonitor实现,其主要结构包括:

  • ContentionList:竞争队列,所有请求锁的线程首先被放在这个队列中
  • EntryList:候选队列,ContentionList中有资格成为候选资源的线程被移动到这里
  • WaitSet:等待集合,调用wait()方法的线程被放置在这里
  • Owner:当前持有锁的线程

重量级锁的工作流程

  1. 线程尝试获取锁,如果成功,成为Owner
  2. 如果失败,线程被封装成ObjectWaiter对象,加入到ContentionList中
  3. 当持有锁的线程释放锁时,会根据特定策略从ContentionList或EntryList中选取一个线程唤醒

重量级锁就像医院的专家号:每个人必须严格排队,即使医生暂时闲着,也得按规矩来。公平,但效率可能不高。

锁升级的全过程

现在我们把整个锁升级过程串联起来:

  1. 初始状态:对象被创建后,处于可偏向状态但未偏向任何线程(匿名偏向)
  2. 第一次加锁:线程A首次进入同步块,使用CAS将线程ID设置到对象头,进入偏向锁状态
  3. 同一线程重入:线程A再次进入同步块,检查对象头中的线程ID与自己一致,直接通过,无需同步操作
  4. 出现竞争:线程B尝试获取锁,发现锁已被线程A偏向
  5. 偏向锁撤销:等待全局安全点,检查线程A状态
  6. 升级轻量级锁:如果线程A仍需要锁,升级为轻量级锁,线程A成为锁持有者,线程B自旋等待
  7. 自旋过度:如果线程B自旋等待时间过长,或又有线程C来竞争锁
  8. 升级重量级锁:轻量级锁升级为重量级锁,线程B和C进入阻塞状态

实战场景分析

场景一:单线程环境(适合偏向锁)

publicclassSingleThreadScenario{publicvoidprocess(){List<String>data=fetchData();synchronized(this){// 处理数据processData(data);}// 其他操作...synchronized(this){// 再次处理furtherProcess(data);}}}

这种情况下,偏向锁可以大幅提升性能,因为同一线程多次获取锁时几乎零开销。

场景二:低竞争环境(适合轻量级锁)

publicclassLowContentionScenario{publicvoidprocess(){ExecutorServiceexecutor=Executors.newFixedThreadPool(2);// 两个线程交替执行,不是同时竞争for(inti=0;i<10;i++){executor.submit(()->{synchronized(this){// 短暂的同步操作shortOperation();}});}}}

这种情况下,轻量级锁通过自旋避免线程阻塞,提高响应速度。

场景三:高竞争环境(需要重量级锁)

publicclassHighContentionScenario{privatefinalObjectlock=newObject();publicvoidhighContentionMethod(){ExecutorServiceexecutor=Executors.newFixedThreadPool(10);// 10个线程激烈竞争同一把锁for(inti=0;i<100;i++){executor.submit(()->{synchronized(lock){// 较长的同步操作longRunningOperation();}});}}}

这种情况下,轻量级锁会导致大量自旋消耗CPU,重量级锁虽然阻塞线程,但总体效率更高。

锁优化的其他技术

除了锁升级,JVM还提供了其他锁优化技术:

锁粗化(Lock Coarsening)

将多个连续的锁操作合并为一个更大范围的锁操作。

// 锁粗化前publicvoidappend(){stringBuffer.append("a");stringBuffer.append("b");stringBuffer.append("c");}// 锁粗化后(JVM自动优化)publicvoidappend(){// 将三次加锁解锁合并为一次synchronized(stringBuffer){stringBuffer.append("a");stringBuffer.append("b");stringBuffer.append("c");}}

锁消除(Lock Elimination)

JVM通过逃逸分析技术,发现某些锁操作不可能被其他线程访问,就会将这些锁操作消除。

publicStringcreateString(){// stringBuffer是局部变量,不可能被其他线程访问StringBufferstringBuffer=newStringBuffer();stringBuffer.append("hello");stringBuffer.append("world");returnstringBuffer.toString();}

这种情况下,JVM会消除StringBuffer内部的同步操作。

如何选择合适的锁策略?

  1. 如果确定是单线程环境:可以开启偏向锁(默认开启)
  2. 如果是低竞争环境:轻量级锁是最佳选择
  3. 如果是高竞争环境:考虑禁用偏向锁和自旋锁,直接使用重量级锁
  4. 极端高并发场景:考虑使用Java并发包中的ReentrantLock等更高级的锁机制

可以通过以下JVM参数进行调优:

  • 关闭偏向锁:-XX:-UseBiasedLocking
  • 关闭自旋锁:-XX:-UseSpinning
  • 批量重偏向阈值:-XX:BiasedLockingBulkRebiasThreshold=20

总结

Java的锁升级机制是一个精美的性能优化方案,它体现了按需分配的思想:根据实际的竞争情况,提供不同级别的锁机制。

偏向锁适用于单线程重复访问的场景,轻量级锁适用于低竞争交替执行的场景,重量级锁适用于高竞争的场景。理解这些锁的工作原理和升级过程,有助于我们编写更高效的并发程序,并在出现性能问题时能准确诊断。

记住,没有绝对的优劣,只有适合的场景。选择合适的锁策略,让你的程序在并发世界中游刃有余!

参考文章

  1. https://blog.51cto.com/universsky/5377002
  2. https://blog.csdn.net/chengyan_1992/article/details/124803701
  3. https://blog.csdn.net/w1475995549/article/details/139992087
  4. https://blog.csdn.net/lp284558195/article/details/115547269
  5. https://blog.csdn.net/MariaOzawa/article/details/107665689

希望这篇文章能帮助你理解Java锁升级机制。如果有任何问题,欢迎在评论区留言讨论!下次我们将深入探讨Java并发包中的其他高级特性,敬请期待!

更多技术干货欢迎关注微信公众号科威舟的AI笔记~

【转载须知】:转载请注明原文出处及作者信息

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/15 12:03:41

强化学习系统性学习笔记(一):从理论基础到策略优化

1.1 理论溯源&#xff1a;从生物学习到数学框架强化学习的思想源头可以追溯到生物行为心理学中的"试错学习"原理。在自然界中&#xff0c;生物个体通过反复尝试不同行为并观察环境反馈&#xff0c;逐步学会选择能够带来更好结果的行动策略。这一朴素而普适的学习模式…

作者头像 李华
网站建设 2026/2/15 15:40:14

想做大数据架构师,HCIP - 大数据认证是必备吗?

不少考了HCIP大数据认证的朋友吐槽&#xff1a;持证面试大数据架构师仍屡屡碰壁&#xff0c;甚至疑惑证书是否没用。结合同行转型经验与招聘实情&#xff0c;核心问题并非证书无效&#xff0c;而是大家缺失了认证之外的关键能力——这正是转型失败的主要原因。 一、先搞懂&…

作者头像 李华
网站建设 2026/2/10 15:47:28

基于微信小程序的校友惠超市管理系统【源码文末联系】

基于微信小程序的校友惠超市管理系统 三个角色&#xff08;管理员&#xff0c;用户&#xff0c;超市&#xff09; 效果如下&#xff1a; 商品信息详情页面 登陆页面 系统首页面 管理员页面 用户管理页面 商品分类页面 商品信息页面 用户首页 研究背景 在高校数字化转型浪潮…

作者头像 李华
网站建设 2026/2/11 11:26:16

Azure AI Search 性能优化实战:从 40 秒到 8 秒的优化之旅

背景 我们的知识库问答系统使用 Azure Container Apps + Azure AI Search + Azure OpenAI 构建,架构如下: 用户请求 → Container App → AI Search (向量搜索) → OpenAI (生成回答)系统上线后,性能表现不佳: P50 响应时间:18 秒 P99 响应时间:41 秒 最慢请求:40.9 秒…

作者头像 李华
网站建设 2026/2/14 22:15:39

碱性电解槽单元槽内流体均匀性设计探秘

碱性电解槽单元槽内流体均匀性设计&#xff0c;目前行业内单元槽主要分为圆形和方形结构单元槽&#xff0c;极板包括平板型&#xff0c;乳突型&#xff0c;棱型凹凸结构&#xff0c;分析单元槽内气液比&#xff0c;速度&#xff0c;压力&#xff0c;湍动能&#xff0c;涡分布&a…

作者头像 李华