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

synchronized底层加锁原理

synchronized加锁主要是通过对象头和监视器实现的,下面我们具体介绍

1. 对象头

Mark Word:这是对象头的主要部分,用于存储对象的运行时元数据,如对象的哈希码、对象的分代年龄、锁状态标志、偏向锁 ID 等信息。它的长度一般为 32 位或 64 位,具体取决于 Java 虚拟机的运行模式和操作系统的位数。在不同的锁状态下,Mark Word 会存储不同的信息,以支持高效的锁机制。
Class Pointer:即对象指向它的类元数据的指针,通过这个指针,虚拟机可以知道该对象属于哪个类,从而能够在运行时获取对象的类信息,如类的方法、字段等。在 64 位虚拟机中,默认情况下,这个指针占用 64 位空间。不过,为了节省内存,虚拟机可能会开启指针压缩功能,此时 Class Pointer 的长度可能会变为 32 位。
 

  • 偏向锁ID:由一个bit存储,1表示偏向锁,0表示非偏向锁
  • 锁状态标志:由两个bit存储,01表示无锁,00表示轻量级锁,10表示重量级锁,11表示GC标记

 线程会根据这两个标记判断这个锁对象是否上锁。

2. 监视器(Monitor)

监视器可以理解成一种同步工具,也可以看作是一种锁的实现。在 Java 里,每个 Java 对象都可以关联一个监视器。当一个线程尝试访问被监视器保护的代码块或方法时,它必须先获取该对象的监视器锁。若该监视器已被其他线程持有,那么这个线程就会进入阻塞状态,直至锁被释放。 

监视器主要由三部分构成:

  • Owner:此为持有监视器锁的线程。同一时间,仅有一个线程能够成为监视器的 Owner。
  • Entry Set:也就是入口集,这里存放着所有尝试获取监视器锁但当前处于阻塞状态的线程。当 Owner 线程释放锁之后,Entry Set 中的线程会竞争获取锁。
  • Wait Set:即等待集,这里存放着那些调用了对象的wait()方法之后进入等待状态的线程。当其他线程调用了相同对象的notify()或者notifyAll()方法时,Wait Set 中的线程会被唤醒,之后它们会和 Entry Set 中的线程一起竞争获取锁。 

监视器的工作原理 

  • 获取锁:当线程执行到synchronized修饰的方法或者代码块时,线程会尝试获取该对象的监视器锁。若锁处于空闲状态(查看对象头的锁标志,偏向锁id),线程就能获取到锁,然后成为监视器的 Owner,进而执行同步代码。
  • 释放锁:当线程执行完同步代码后,会释放持有的监视器锁。若此时 Entry Set 中有等待的线程,这些线程会竞争获取锁。
  • 等待和唤醒:当线程在同步代码中调用对象的wait()方法时,线程会释放持有的监视器锁,然后进入该对象的 Wait Set。当其他线程调用相同对象的notify()或者notifyAll()方法时,Wait Set 中的线程会被唤醒,之后它们会和 Entry Set 中的线程一起竞争获取锁。

3. 细节补充 

3.1 获取锁

当线程执行到 synchronized 修饰的方法或者代码块时,线程会尝试获取该对象的监视器锁。这一过程在不同锁状态下具体细节有所不同:

  • 无锁状态:无锁状态下,对象头的 Mark Word 存储对象的哈希码、分代年龄等信息。当第一个线程访问同步块并尝试获取锁时,会检查对象头的锁标志位。如果是无锁状态,会通过 CAS(Compare - And - Swap)操作将线程 ID 记录到对象头的 Mark Word 中,同时将锁标志位设置为偏向锁,此时该线程成为监视器的 Owner。
  • 偏向锁状态:会检查 Mark Word 中记录的偏向线程 ID 是否是当前线程。如果是,则直接获取锁;如果不是,则需要撤销偏向锁,将对象头恢复到无锁状态或者升级为轻量级锁,再重新竞争锁。
  • 轻量级锁状态:线程在进入同步块时,会在当前线程的栈帧中创建一个锁记录(Lock Record),然后通过 CAS 操作尝试将对象头的 Mark Word 复制到锁记录中,并将对象头的 Mark Word 指向锁记录的地址。如果 CAS 操作成功,线程获取到轻量级锁,成为监视器的 Owner;如果失败,表示有其他线程已经持有轻量级锁,当前线程会进行自旋等待,若自旋一定次数后仍未获取到锁,轻量级锁会升级为重量级锁。
  • 重量级锁状态:重量级锁依赖于操作系统的互斥量(Mutex)实现。当线程尝试获取重量级锁时,如果锁处于空闲状态,线程可以直接获取;如果锁已被其他线程持有,线程会被阻塞,进入 Entry Set 等待。
3.2 释放锁

当线程执行完同步代码后,会释放持有的监视器锁。释放锁的过程同样与锁状态有关:

  • 偏向锁释放:偏向锁在没有竞争的情况下不会主动释放,只有当有其他线程尝试竞争锁时,持有偏向锁的线程才会在安全点(Safe Point)上撤销偏向锁。
  • 轻量级锁释放:线程退出同步块时,会使用 CAS 操作将锁记录中的 Mark Word 复制回对象头。如果 CAS 操作成功,说明没有发生锁竞争,轻量级锁释放成功;如果失败,说明在释放锁的过程中发生了锁竞争,轻量级锁已经升级为重量级锁,需要按照重量级锁的方式释放。
  • 重量级锁释放:持有重量级锁的线程执行完同步代码后,会释放操作系统的互斥量,唤醒 Entry Set 中等待的线程,这些线程会竞争获取锁。
3.3 等待和唤醒
  • notify() 方法:会随机唤醒 Wait Set 中的一个线程,被唤醒的线程需要重新竞争锁,只有竞争到锁才能继续执行。
  • notifyAll() 方法:会唤醒 Wait Set 中的所有线程,这些线程会和 Entry Set 中的线程一起竞争锁。

http://www.kler.cn/a/591025.html

相关文章:

  • HTTP服务器的工作逻辑
  • 力扣hot100_二分查找(1)_python版本
  • 小样本学习入门指南:以图像识别为例
  • 【数据结构之树】
  • PE(Processing Element,处理单元)在Vitis HLS中的应用与实现
  • 深入理解 Linux 的 top 命令:实时监控系统性能
  • Python绝美樱花树
  • 结合基于标签置信度的特征选择方法用于部分多标签学习-简介版
  • 第18章-综合以上功能 基于stm32的智能小车(远程控制、避障、循迹) 基于stm32f103c8t6_HAL库_CubeMX_超详细,包含代码讲解和原理图
  • Matlab 汽车电子驻车系统仿真分析
  • Java算法之解题套路
  • 超图神经网络的详细解析与python示例
  • 国产编辑器EverEdit - 模式的原理与作用
  • HP LoadRunner 12.02全面性能测试工具的功能与使用指南
  • 【大模型】Token计算方式与DeepSeek输出速率测试
  • 本周安全速报(2025.3.11~3.17)
  • 【深度学习与大模型基础】第6章-对角矩阵,对称矩阵,正交矩阵
  • Redis--渐进式遍历
  • 清晰易懂的Python安装与配置教程
  • 磁盘分析“透视镜”,轻松管理存储空间!