HelloCoder HelloCoder
首页
《Java小白求职之路》
《小白学Java》
计算机毕设
  • 一些免费计算机资源
  • 脚手架工具
  • 《从0到1学习Java多线程》
  • 《从0到1搭建服务器》
  • 《可观测和监控》
随笔
关于作者
首页
《Java小白求职之路》
《小白学Java》
计算机毕设
  • 一些免费计算机资源
  • 脚手架工具
  • 《从0到1学习Java多线程》
  • 《从0到1搭建服务器》
  • 《可观测和监控》
随笔
关于作者
  • 《LearnJavaToFindAJob》

    • 导读

    • 【初级】6~12k档

    • 【中级】12k-26k档

      • JVM进阶

      • Java进阶

        • ConcurrentHashMap面试题
        • CopyOnWriteArrayList的实现原理
        • HashMap的put过程是怎么样的?
        • IO模型有哪些?
        • Synchronized相关
        • ThreadLocal的原理
        • synchronized锁升级
        • valueOf、(String)强转有什么区别?
        • 为什么在lambda中使用的局部变量必须是final或有效final
        • 你知道如何更新缓存吗?如何保证缓存和数据库双写一致性?
        • 八股文之ReentrantLock
        • 分布式事务
        • 如何保障生产端消息投递成功?
        • 如何手动触发全量回收垃圾,如何立即触发垃圾回收
        • 数据库连接池为什么要使用ThreadLocal?
        • 线程池中多余的线程是如何回收的
        • 谈谈你对AQS的理解
        • 谈谈你对CAS的理解
        • util.concurrent的理解
        • 高并发下如何保证接口的幂等性?
      • MySQL

      • 中间件

      • 算法

      • 高阶

    • 【高级】26k+档

    • 大厂面试题

    • 求职建议

    • 面经

  • LearnJavaToFindAJob
  • 【中级】12k-26k档
  • Java进阶
#synchronized #锁升级
码农阿雨
2025-12-04
目录

synchronized锁升级

锁升级过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

# 各jdk版本锁

在jdk1.5版本(包含)之前,锁的状态只有两种状态:“无锁状态”和“重量级锁状态”,只要有线程访问共享资源对象,则锁直接成为重量级锁。因为加锁和释放锁的过程JVM的底层都是由操作系统mutex lock来实现的,其中会涉及上下文的切换(即用户态和内核态的转换),性能消耗极其高,所以在当时synchronized锁是公认的重量级锁。性能消耗非常大。后来JVM开发团队为解决性能问题,在jdk1.5版本中加入了JUC并发包,包下开发了很多Lock相关的锁。

jdk1.6版本后,对synchronized锁进行了优化,新加了“偏向锁”和“轻量级锁”,偏向锁是默认开启的,用来减少上下文的切换以提高性能,通过锁的升级来解决不同并发场景下的性能问题。所以锁就有了4种状态。

在 JDK 15 中,但由于偏向锁其撤销成本较高,偏向锁被默认禁用了。因为很多开发者发现,对于竞争激烈的场景,偏向锁带来的收益小于其撤销成本。

对象头(Mark Word)存储的内容:

解释:

无锁:对于共享资源,不涉及多线程的竞争访问,就不会加锁

# 锁升级流程

第一阶段:无锁 -> 偏向锁

  • 触发条件:一个线程第一次进入同步块。

  • 目标:在没有竞争的情况下,消除同步原语的开销。这个锁会“偏向”于第一个获取它的线程。

  • 过程:

    • 线程 A 访问同步块,检查锁标志位为 01,且偏向锁位为 0(表示可偏向)。
    • 使用 CAS 操作,尝试将 Mark Word 中的 ThreadID 替换为自己的线程 ID。
    • 如果 CAS 成功,线程 A 就持有了偏向锁。之后,只要线程 A 再次进入这个同步块,它只需要检查 Mark Word 中的 ThreadID 是否是自己,如果是,则无需任何同步操作,直接执行。
  • 优点:对于同一个线程重复获取锁的场景,性能极高,几乎没有额外开销。

  • 撤销:当有另一个线程 B 来尝试竞争这个锁时,偏向模式就宣告结束。JVM 需要撤销偏向锁。这个过程需要等待全局安全点,然后暂停持有偏向锁的线程 A,检查 A 是否存活或已退出同步块。然后根据情况,将锁升级为轻量级锁或恢复到无锁状态。

    注意:在 JDK 6 之后,偏向锁是默认开启的,但由于其撤销成本较高,在 JDK 15 中,偏向锁被默认禁用了。因为很多开发者发现,对于竞争激烈的场景,偏向锁带来的收益小于其撤销成本。

第二阶段:偏向锁 -> 轻量级锁

  • 触发条件:当偏向锁被撤销后,如果有线程竞争,但竞争是轻度的(即线程交替执行同步块,没有同时竞争)。
  • 目标:在线程交替执行的场景下,避免直接使用重量级锁带来的用户态/内核态切换开销。
  • 过程:
    • 线程 A 持有偏向锁,线程 B 来竞争。
    • 偏向锁被撤销。
    • 线程 A 和 B 都会在自己的栈帧中创建一个名为 锁记录(Lock Record) 的空间。
    • 线程 A 首先将当前 Mark Word 的内容复制到自己的锁记录中(称为 Displaced Mark Word)。
    • 然后,线程 A 尝试用 CAS 操作将对象头的 Mark Word 替换为指向自己锁记录的指针。
    • 如果成功,线程 A 就获取了轻量级锁。
    • 此时,线程 B 也会进行同样的 CAS 操作来尝试获取锁,但会失败(因为 Mark Word 已被线程 A 修改)。失败后,线程 B 会自旋(循环重试 CAS)一小段时间来尝试获取锁。
    • 如果在线程 B 自旋期间,线程 A 释放了锁(通过 CAS 将 Displaced Mark Word 写回对象头),那么线程 B 的 CAS 就可能成功,从而获取到锁。这样,线程间就没有发生阻塞。
  • 优点:竞争的线程不会阻塞,通过自旋来提高响应速度。

升级:如果线程 B 自旋了一定次数后(JDK 6 引入了自旋适应的策略,一般默认锁10次 ?),还没能获取到锁,或者此时有第三个线程来竞争,说明竞争加剧了。此时,轻量级锁就会升级为重量级锁。

第三阶段:轻量级锁 -> 重量级锁

  • 触发条件:轻量级锁竞争失败(自旋失败或有多线程竞争)。
  • 目标:处理重度竞争的场景。
  • 过程:
    • JVM 会申请一个操作系统层面的 互斥量(Mutex Lock),也称为监视器锁(Monitor)。
    • 将 Mark Word 的内容替换为指向这个互斥量的指针。
    • 此时,所有未竞争到锁的线程(如线程 B),都会被挂起,进入操作系统的等待队列中。
    • 等待锁被释放后,操作系统会负责唤醒这些被挂起的线程,让它们重新竞争。
  • 缺点:开销巨大。涉及到操作系统内核态的切换,线程的挂起和唤醒都是很耗时的操作。
  • 优点:在激烈竞争下,避免了 CPU 空转(无休止的自旋)。

阅读全文
×

(为防止恶意爬虫)
扫码或搜索:HelloCoder
发送:290992
即可永久解锁本站全部文章

解锁
#synchronized#锁升级
上次更新: 2025-12-04 03:34:09
最近更新
01
MySQL支持的锁有哪些
12-04
02
Synchronized相关
12-04
03
如何停止一个运行的线程
12-04
更多文章>
Theme by Vdoing | Copyright © 2020-2025 码农阿雨
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式