当前位置: 首页 > news >正文

介绍一下synchronized锁升级过程

分析:

首先介绍一下synchronized

synchronized 是 Java 中用于实现线程同步的关键字、它通过一种称为锁升级的机制来优化性能、根据锁的竞争情况动态调整锁的状态。

synchronized 的核心是基于 monitor 锁、也称为 监视器锁互斥锁monitor 是一种重量级的锁机制,它的实现依赖于操作系统提供的同步原语(如互斥量 mutex

Monitor 对象:

  • 在 Java 中 每个对象都有一个与之关联的 monitor 它存储在对象的 对象头 中:

对象头通常包含以下内容:

  • Mark Word:存储对象的哈希码、锁状态、GC 标记等信息。

  • Class Metadata Address:指向对象的类元数据。

  • Array Length(如果是数组):存储数组的长度。

    • 这个 Monitor 对象可以理解为一把锁。

    • Monitor 对象包含 _owner (指向持有锁的线程),

monitor 锁有以下几种状态:

  • 无锁状态:对象未被锁定。

  • 偏向锁:偏向于一个线程、减少锁的开销。

  • 轻量级锁:使用自旋锁、减少线程阻塞。

  • 重量级锁:当自旋失败后、线程进入阻塞状态、使用操作系统级别的锁。

  • 共享锁:允许多个线程同时访问(如读写锁的读锁)。

JDK 1.6 引入了锁升级机制 根据不同的竞争情况选择不同的锁:

默认就是无锁状态:对象没有被任何线程锁定。Mark Word 记录的是对象的哈希码等信息。

在大多数情况下、锁不仅不存在多线程竞争、而且总是由同一个线程多次获得。偏向锁就是为了优化这种场景。

后面引入了偏向锁

  • 当一个线程(比如线程A)第一次进入一个 synchronized 代码块并尝试获取锁时、如果该对象是无锁状态、并且偏向锁是启用的(默认启用)、JVM 会将对象头的 Mark Word 中的锁标志位(表示偏向锁)、同时用 CAS 操作尝试将当前线程A的ID记录到 Mark Word 中

    • 如果成功: 线程A就持有了该对象的偏向锁。之后线程A再次进入与此锁相关的 synchronized块时、只需要检查 Mark Word 中的线程ID是否是自己,如果是则无需任何额外的同步操作(如CAS)、直接执行同步代码、效率极高。这就是偏向锁的偏向之处。

    • 如果失败 (CAS竞争或已有偏向):说明有其他线程竞争偏向锁、此时会尝试撤销偏向锁。撤销偏向锁后、锁会膨胀为轻量级锁。

然后到了轻量级锁

  • 引入目的: 当偏向锁被撤销、或者多个线程交替执行同步代码块、但几乎没有实际的同时竞争时、使用轻量级锁可以避免重量级锁的操作系统内核态切换开销。

  • 升级过程与判断:

    • 尝试获取锁的线程(比如线程A)会在自己的栈帧中创建一个锁记录 (Lock Record)、用于存储对象 Mark Word 的一个拷贝(Displaced Mark Word)。

    • 然后线程A会尝试使用 CAS ) 操作将对象头的 Mark Word 更新为指向其栈帧中这个锁记录的指针。同时锁记录中的 owner 指针会指向对象。

    • 如果CAS成功: 线程A成功获取轻量级、Mark Word 的锁标志位会变为00

    • 如果CAS失败: 说明在它尝试CAS期间、已经有其他线程先一步获取了轻量级锁

  • 自旋等待:

    • 获取轻量级锁失败的线程并不会立即阻塞、而是会进行自旋 、自旋锁是一种非阻塞式的锁实现方式、线程在尝试获取锁失败时不会进入阻塞或者挂起状态、而是通过循环自旋的方式等待锁的释放

    • JVM 会进行自适应自旋、即自旋的次数和时间会根据以往自旋成功的情况动态调整。

    • 自旋锁可以避免线程切换的开销、如果锁被其他线程长时间占用、自旋线程会一直占用 CPU 资源、造成浪费。

  • 锁膨胀:

    • 如果自旋一定次数后仍然无法获取锁(例如持有锁的线程执行时间较长、或者有其他线程也在自旋竞争),轻量级锁就会膨胀为重量级锁。

重量级锁

  • 引入场景: 当多个线程激烈竞争同一个锁、自旋等待无法有效解决问题时、就会升级到重量级锁。

  • 实现: 重量级锁依赖于对象的 Monitor 实现、而 Monitor 又依赖于操作系统的互斥量 (Mutex)。

  • 过程:

    • Mark Word 的锁标志位会变为1、并指向一个真正的 Monitor 对象

    • 线程获取重量级锁失败后会被阻塞、并放入该 Monitor 的等待队列中、其状态会从运行态切换到阻塞态、等待操作系统进行线程调度和唤醒。

    • 当持有锁的线程释放锁时、会唤醒等待队列中的一个或多个线程来竞争锁。

  • 开销: 重量级锁涉及用户态到内核态的切换以及线程的阻塞和唤醒、开销较大。

总结锁升级路径:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

这个过程是单向的、通常只能升级、不能降级

适用场景如下:

  • 偏向锁: 适用于只有一个线程访问同步代码块的场景。 线程第一次获取锁时、会将对象头设置为偏向模式、后续该线程再次进入同步块时、无需进行任何同步操作、从而避免了额外的开销。
  • 轻量级锁: 适用于多个线程交替访问同步代码块、但竞争不激烈的场景。
  • 重量级锁线程的竞争不使用自旋、不会消耗CPU但是重量级的开销比较大、因为涉及到用户态和内核态的切换。响应时间慢

参考面试回答

介绍一下synchronized锁升级过程

首先介绍一下synchronized

  • synchronized 是 Java 中用于实现线程同步的关键字
  • synchronized 的核心是基于 monitor 锁、也称为 监视器锁互斥锁monitor 是一种重量级的锁机制,它的实现依赖于操作系统提供的同步原语(如互斥量 mutex
  • Monitor 对象: 是在 Java 中 每个对象都有一个与之关联的 monitor 它存储在对象的 对象头 中:
  • 锁的状态记录在对象头里的(Mark Word)中

为提升性能、JVM 根据竞争情况引入了 锁的升级机制、一共有四种状态:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

过程如下:

默认是无锁状态、Mark Word 存储哈希值等元信息、无线程持有锁。

然后到了偏向锁

  • 当线程第一次获取锁时、JVM 会将该线程ID写入对象头的 Mark Word 中。

  • 之后该线程再次进入同步块时、只需比对线程ID,无需加锁操作,效率极高。

  • 当其他线程尝试获取锁时、偏向锁会被撤销、进入锁升级流程。

升级到轻量级锁

  • 线程尝试加锁时、会复制对象头的 Mark Word 到自己的栈中叫锁记录

  • 然后尝试使用 CAS 将对象头指向自己的锁的记录、表示自己持有锁。

  • 如果 CAS 失败:说明有其他线程已持有锁 → 自旋尝试获取锁(避免线程阻塞)。

  • 如果自旋一定次数后仍然无法获取锁(例如持有锁的线程执行时间较长、或者有其他线程也在自旋竞争)、轻量级锁就会膨胀为重量级锁。

✅ 重量级锁

  • 自旋失败后,锁膨胀为重量级锁、线程会被阻塞挂起、等待操作系统调度。

  • 对象头的 Mark Word 指向 Monitor 对象,Monitor 会维护一个等待队列。

  • 开销大、涉及用户态与内核态切换、响应慢。适用场景: 多线程同时竞争锁

相关文章:

  • 设计模式【cpp实现版本】
  • 不同环境下运行脚本如何解决pythonpath问题
  • 【 Redis | 实战篇 缓存 】
  • 非阻塞式IO-Java NIO
  • tryhackme——Enumerating Active Directory
  • Github 2025-05-10 Rust开源项目日报 Top10
  • Webug4.0通关笔记25- 第30关SSRF
  • 【Android】文件分块上传尝试
  • vue注册用户使用v-model实现数据双向绑定
  • Kotlin 协程 vs RxJava vs 线程池:性能与场景对比
  • Spring boot 简单开发接口
  • 超详细fish-speech本地部署教程
  • LLaVA:开源多模态大语言模型深度解析
  • 数据结构中的栈与队列:原理、实现与应用
  • C++GO语言微服务和服务发现②
  • 【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)高级用法(二)
  • Java数据结构——二叉树
  • 封装 RabbitMQ 消息代理交互的功能
  • 【C++ Qt】容器类(GroupBox、TabWidget)内附思维导图 通俗易懂
  • 【算法-哈希表】常见算法题的哈希表套路拆解
  • 价格周报|供需回归僵局,本周生猪均价与上周基本持平
  • 游戏论|暴君无道,吊民伐罪——《苏丹的游戏》中的政治
  • 上海发布首份直播电商行业自律公约,禁止虚假宣传、商业诋毁
  • 央行设立服务消费与养老再贷款,额度5000亿元
  • 2025中国南昌国际龙舟赛5月23日启幕,是历年来南昌举办的最高规格龙舟赛事
  • “降息潮”延续!存款利率全面迈向“1时代”