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

介绍一下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 会维护一个等待队列。

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

http://www.xdnf.cn/news/5244.html

相关文章:

  • 2024年AI发展趋势全面解析:从多模态到AGI的突破
  • LintCode第485题-生成给定大小的数组,第220题-冰雹猜想,第235题-分解质因数
  • JDBC演进之路:从基础操作到高效连接池
  • 计算机科技笔记: 容错计算机设计03 系统可信性的度量 偶发故障期 浴盆曲线 韦布尔分布
  • 工程师视角下的 AI 浏览器智能体拆解(AI Browser Agent from an Engineer‘s Perspective)
  • TWAS、GWAS、FUSION
  • 使用Simulink开发Autosar Nvm存储逻辑
  • Qt开发经验 --- 避坑指南(11)
  • Ctrl + D是如何与内核文件结束符对应的?如何模拟文件结束符?数字中间为什么不能插入空格或逗号?丰富多彩的语句结束符或分隔符?语句结束符?
  • (剪映)视频添加字幕
  • SEO长尾关键词优化实战技法
  • 利用大型语言模型有效识别网络威胁情报报告中的攻击技术
  • webpack和vite区别
  • Go语言超时控制方案全解析:基于goroutine的优雅实现
  • 移动零--双指针
  • Docker 使用总结及完整示例介绍
  • 【天府诸葛杯】九天画芯 FSHD 三色光源技术晋级复赛:突围下一代底层显示技术
  • 数仓-如何保障指标的一致性
  • MySQL 索引和事务
  • 电子电路:光子是不是粒子?
  • 基于OpenCV的人脸识别:FisherFaceRecognizer算法
  • SolidWork-2023 鼠標工程
  • Java集合
  • Qt中的RCC
  • 如何避免在CMD中分段发送问题导致大模型多段回复的问题?
  • Day115 | 灵神 | 二叉树 | 二叉搜索树中的众数
  • Redis 哨兵
  • DIP依赖倒置原则
  • 第十课认识约数
  • 蓝牙身份证阅读器使用Uniapp调用二次开发demo