从Hotspot源码视角揭秘synchronized的底层实现


从Hotspot源码视角揭秘synchronized的底层实现

synchronized在Java中通过JVM的内置锁机制实现线程同步,而Hotspot虚拟机(Oracle JDK默认JVM)的底层实现尤为复杂。本文将从对象头、锁状态转换、Monitor机制、源码流程等角度深入解析。


一、对象头与锁状态:锁的"身份证"

每个Java对象在内存中都有一个对象头 ,其中关键部分是Mark Word,它动态存储锁状态、哈希码、GC分代年龄等信息。在Hotspot中,锁状态通过Mark Word的位模式标识,分为四种状态:

  1. 无锁:初始状态,Mark Word存储哈希码和分代年龄。
  2. 偏向锁:记录线程ID和epoch(偏向锁版本),适用于单线程重复访问的场景。
  3. 轻量级锁:存储指向线程栈中锁记录(Lock Record)的指针,通过CAS竞争实现。
  4. 重量级锁:Mark Word指向操作系统级互斥量(Mutex),线程竞争失败后进入阻塞队列。

锁升级流程:

  • 无锁 → 偏向锁:首次线程访问时,JVM启用偏向锁(需等待4秒延迟,可通过参数关闭)。
  • 偏向锁 → 轻量级锁:检测到多线程竞争时撤销偏向锁,通过CAS自旋尝试获取锁。
  • 轻量级锁 → 重量级锁:自旋失败后升级为重量级锁,调用操作系统互斥量。

二、Monitor机制:锁的"调度中心"

每个Java对象关联一个ObjectMonitor(C++实现),负责管理锁竞争和线程调度,核心字段包括:

  • _owner:持有锁的线程。
  • _recursions:锁重入次数。
  • _EntryList:竞争锁失败的线程队列。
  • _WaitSet:调用wait()进入等待的线程队列。

锁竞争流程(以重量级锁为例):

  1. 尝试快速获取锁 :CAS设置_owner为当前线程,成功则直接进入临界区。
  2. 锁重入 :若线程已持有锁,_recursions自增。
  3. 加入队列 :竞争失败后,线程封装为ObjectWaiter节点插入_cxq队列(头插法),并通过自旋多次尝试获取锁(自适应自旋优化)。
  4. 挂起线程 :最终失败后调用pthread_mutex_lock进入内核态阻塞。

锁释放流程:

  1. _recursions减1,若归零则清空_owner。
  2. 根据QMode策略唤醒_EntryList或_cxq队列中的线程(如优先唤醒队首或合并队列)。

三、源码级流程:从monitorenter到monitorexit

synchronized代码块编译后生成monitorenter和monitorexit指令,Hotspot通过解释器和模板解释器处理这些指令:

  1. 偏向锁快速路径 :
    • 检查Mark Word是否为偏向模式且线程ID匹配。
    • 若匹配,直接进入临界区,无需CAS操作。
  2. 轻量级锁CAS竞争 :
    • 在线程栈中创建Lock Record,拷贝对象头信息。
    • 通过CAS将Mark Word替换为Lock Record指针,成功则获取锁。
  3. 重量级锁进入 :
    • 调用ObjectMonitor::enter(),触发锁竞争和队列管理。

异常处理:

  • monitorexit指令在代码正常结束和异常路径均会被插入,确保锁释放。

四、性能优化:Hotspot的"黑科技"

  1. 锁消除(Lock Elision) :
    JVM通过逃逸分析判断锁对象未逃逸时,直接删除同步代码(如局部StringBuffer的同步块)。
  2. 锁粗化(Lock Coarsening) :
    合并相邻同步块,减少锁获取/释放次数(如循环内的多次同步合并为一次)。
  3. 自适应自旋 :
    根据历史自旋成功率动态调整自旋次数,避免CPU空转。
  4. 偏向锁延迟启用 :
    默认延迟4秒启用偏向锁,避免启动时大量锁竞争导致频繁撤销。

五、设计哲学:性能与公平性的权衡

  • 偏向锁:牺牲公平性换取无竞争场景的性能,但JDK 15后默认禁用(因维护成本高)。
  • 轻量级锁:通过CAS减少内核态切换,适合低竞争短耗时场景。
  • 重量级锁:保证公平性,但线程阻塞唤醒开销大,适合高竞争场景。

总结

Hotspot中synchronized的实现是软件与硬件协同的杰作:

  • 对象头 动态记录锁状态,Monitor机制 管理线程调度,锁升级平衡性能与公平性。
  • 理解这些底层细节,能更好地优化多线程代码(如减少锁粒度、避免嵌套锁),并在面试中游刃有余(如锁升级过程、synchronized vs ReentrantLock对比)。

参考资料:

相关推荐
Wang's Blog13 小时前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx
\光辉岁月/14 小时前
5.java-数组
java·开发语言
谢亮_vipxieliang14 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
郑州光合科技余经理15 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
2601_9620715715 小时前
类变量和全局变量的生命周期有什么区别?
java·开发语言·jvm
code2cat15 小时前
Java进阶篇之AtomicReference:一次替换引用,读到一份完整状态
java·并发编程·不可变对象·atomicreference
专业程序开发源15 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
Wang's Blog15 小时前
Java 项目实战: 外卖平台优化-前后端分离开发模式与工程拆分
java·开发语言
HEJOO916 小时前
指针的算术运算详解
java·数据结构·算法
sunshine22 girl16 小时前
Java学习六 Java常见API-2--String-3-字符串的截取替换和其他方法
java·开发语言·学习