并发编程原理与实战(二十七)深入剖析synchronized底层基石ObjectMonitor与对象头Mark Word

上一篇文章中,我们深入分析了synchronized底层原理实现,其中分析了什么是对象的监视器‌,如何查看对象监视器‌的状态信息、显式隐式获取监视器锁等问题。本文继续深入分析synchronized底层原理实现,解决以下几个问题。

(1)synchronized以什么作为互斥?

(2)synchronized既然可重入,那么重入计数记录在哪里?

(3)持有监视器锁的线程信息记录在哪里?

(4)其他抢锁的等待者记录在哪里?

(5)java对象(锁对象)如何关联Monitor对象?

深入Monitor

Monitor实现并发协同实现原理

synchronized既然是一个独占锁,就像数据库的主键字段不能重复,同一个文件夹下文件名不能重复,那么它的互斥属性是怎么实现的?

synchronized基于Monitor实现,而Monitor是一种编程语言层面的高级的同步机制,用于在多线程环境下管理共享资源的访问,其核心机制包括互斥与同步两部分。Monitor的互斥属性通过内置的互斥锁(Mutex)实现,保证同一时刻仅有一个线程能进入Monitor内部(临界区)执行操作,其他线程需在等待队列中等待。前面我们已经知道,synchronized与wait/notify实现的并发协同是基于条件判断实现的,这个正是基于Monitor的条件变量实现线程间的协作,Monitor内部维护多个队列(如入口队列、条件队列)管理线程状态,确保并发协同的正确性。

互斥锁(Mutex,全称 ‌Mutual Exclusion‌)是操作系统层面用于控制多线程对共享资源访问的同步机制,其核心目标是确保同一时刻仅有一个线程能进入临界区。

综上所述,我们得出这个依赖关系:synchronized--->Monitor--->Mutex。

JVM ObjectMonitor结构体

synchronized是JVM层面上的锁,而JVM是c/c++写的,在JDK的源码中有对象监视器的结构体ObjectMonitor的定义,ObjectMonitor是HotSpot虚拟机中实现Java对象监视器的核心数据结构。在JDK的源码路径下:

复制代码
jdk-master\jdk-master\src\hotspot\share\runtime\objectMonitor.cpp
jdk-master\jdk-master\src\hotspot\share\runtime\objectMonitor.hpp

我们摘取其中的构造函数进行分析:

c++ 复制代码
ObjectMonitor::ObjectMonitor() {
  _header       = NULL;      // 存储关联对象的Mark Word初始值
  _count        = 0;         // 记录锁的重入次数
  _waiters      = 0;         // 等待线程计数器(如调用wait()的线程)
  _recursions   = 0;         // 锁的重入深度
    
  _object       = NULL;      // 关联的Java对象指针
  _owner        = NULL;      // 持有锁的线程指针
    
  _cxq          = NULL;      // 竞争锁失败的线程栈(FILO结构)
  _EntryList    = NULL;      // 阻塞线程的双向链表(锁分配缓冲区)
  _WaitSet      = NULL;      // 调用wait()的线程等待队列
  _WaitSetLock  = 0;         // 保护_WaitSet操作的锁
    
  _SpinFreq     = 0;         // 自旋锁优化参数
  _SpinClock    = 0;         // 自旋锁时间控制
}

如上所示,其构造函数初始化了以下关键字段:

1、基本状态字段‌

(1)header:存储锁对象的Mark Word原始值,初始为NULL。

(2)count:记录线程获取锁的次数,初始为0。

(3)waiters:等待线程计数器,初始为0。

(4)_recursions:锁重入次数,初始为0。

2、线程管理队列‌

(1)cxq:竞争锁失败的线程单向链表(FILO栈结构),初始为NULL。

(2)EntryList:阻塞线程的双向循环链表,作为锁分配的缓冲区,初始为NULL。

(3)_WaitSet:调用wait()的线程等待队列,初始为NULL。

3、所有权标识‌

(1)owner:指向持有锁的线程,初始为NULL。

(2)object:关联的Java对象指针,初始为NULL。

4、辅助控制字段‌

(1)WaitSetLock:保护WaitSet操作的锁,初始为0。

(2)SpinFreq和SpinClock:*自旋锁优化相关参数,初始为0。

该构造函数通过清零所有字段完成初始化,为后续锁竞争(如monitorenter指令)和线程同步(如wait/notify)提供基础支持。其设计体现了Monitor的互斥与同步机制,通过队列管理实现线程阻塞和唤醒。

对象头

结构体ObjectMonitor各个字段的定义解答了文章开头的第1、第2、第3、第4个问题,还剩"java对象(锁对象)如何关联Monitor对象?"这个问题没有解决。

我们注意到,结构体ObjectMonitor中的_header字段存储关联对象的Mark Word初始值。那么什么是Mark Word?要了解Mark Word得先了解java的对象头。

Java对象头是JVM实现对象内存管理、锁机制和GC的关键数据结构,其组成与锁状态动态关联。Java对象核心结构由以下几部分组成。

组成部分 大小(64位JVM) 功能描述
Mark Word 8字节 动态存储对象的运行时数据,包括哈希码(HashCode)、GC分代年龄、锁状态标志、偏向线程ID、锁指针等
Klass Pointer 4/8字节 指向方法区中对象的类元数据,用于确定对象所属类型
Array Length 4字节(可选) 仅数组对象存在,记录数组长度

Mark Word与锁优化

Mark Word部分存储着锁状态信息,而锁状态有无锁、偏向锁、轻量级锁、重量级锁这几种状态。所以对象头的Mark Word是synchronized锁机制的关键实现。锁状态的变化遵循‌不可逆‌的过程:‌无锁 → 偏向锁 → 轻量级锁 → 重量级锁。

(1)无锁状态。对象刚创建且未被任何线程加锁时处于无锁状态。锁标志位为01。

(2)偏向锁。首次有线程访问同步块时,升级为偏向锁;第一个线程访问同步块,且无竞争;消除单线程重复加锁的开销。锁标志位变为101。

(3)轻量级锁。第二个线程尝试获取锁(发生竞争),偏向锁撤销并升级为轻量级锁。锁标志位变为00。

(4)重量级锁。当CAS自旋失败(默认10次)或竞争加剧(如第三个线程加入),升级为重量级锁。对象头指向操作系统级互斥量(monitor),锁标志位变为10。

锁标志位状态转换示意流程:

java 复制代码
无锁 (01) 
  │
  ↓ (首次单个线程访问)
偏向锁 (101) 
  │
  ↓ (多个线程访问,竞争发生)
轻量级锁 (00) 
  │
  ↓ (多个线程访问,CAS自旋失败或竞争加剧)
重量级锁 (10)

在《并发编程原理与实战(二十三)StampedLock应用实战与各种锁性能对比分析》这篇文章中,我们对比了包括synchronized在内的四种锁的性能,其中synchronized的表现最好。从上述锁标志位状态转换过程可以看出,根据竞争激烈程度自动调整锁策略,这其实是一个synchronized锁优化的结果。

线程执行synchronized(obj)时,JVM会检查obj对象的Mark Word锁标志位。当锁需要升级为重量级锁时,则创建或关联已有的ObjectMonitor,并将Mark Word更新为指向该Monitor的指针。通过这个过程实现java对象(锁对象)与Monitor对象的关联。

总结

本文深入分析了synchronized底层实现依赖,分析了JVM ObjectMonitor结构体的组成,最后分析了java对象头的Mark Word组成部分,简要分析了synchronized锁优化过程以及java对象(锁对象)与Monitor对象的关联的过程。synchronized的底层实现涉及的内容很多,就拿对象头‌Mark Word来讲,其组成部分就有很多项,我们的主要目的还是围绕问题来分析,在此我们不做更多深入的分析。

相关推荐
imHanweihu2 小时前
基于POI-TL实现动态Word模板数据填充(含图表):从需求到落地的完整开发实践
java·onlyoffice·poi-tl
月夕·花晨2 小时前
Gateway -网关
java·服务器·分布式·后端·spring cloud·微服务·gateway
失散132 小时前
分布式专题——6 Redis缓存设计与性能优化
java·redis·分布式·缓存·架构
杏花春雨江南2 小时前
Spring Cloud Gateway 作为一个独立的服务进行部署吗
java·开发语言
GSDjisidi2 小时前
东京本社招聘 | 财务负责人 & 多个日本IT岗位(Java/C++/Python/AWS 等),IT营业同步招募
java·开发语言·aws
叫我阿柒啊2 小时前
Java全栈开发面试实战:从基础到微服务的完整技术栈解析
java·spring boot·微服务·前端框架·vue·jwt·全栈开发
前行的小黑炭2 小时前
Android:在项目当中可能会遇到的ANR,应该如何解决?
android·java·kotlin
索迪迈科技3 小时前
Flink Task线程处理模型:Mailbox
java·大数据·开发语言·数据结构·算法·flink