在Java并发编程中,synchronized关键字是开发者最常用也是最基础的同步工具。然而,许多人对其认知仍停留在“重量级锁”、“性能差”的刻板印象中。理解Synchronized底层Monitor机制的核心价值在于,它揭示了一个为应对真实并发场景而不断自我优化的动态过程——从偏向锁到轻量级锁,再到重量级锁的智能升级路径。这不仅是JVM性能优化的杰作,更是深入理解Java对象内存布局、线程调度和并发控制思想的绝佳窗口。作为鳄鱼Java的资深内容编辑,我将带你穿透关键字本身,直抵其高效运转的内核——Monitor。
一、超越关键字:为什么必须了解Monitor机制?

当我们写下 synchronized(object) { ... } 时,到底发生了什么?简单回答是“加锁”,但JVM实现了一个精妙的模型来支撑这个语义——Monitor(管程或监视器锁)。它是一种同步工具,或者说是一种设计模式,用于在多线程环境下,保证同一时刻只有一个线程能进入临界区(被同步的代码块)。
在JDK 1.6之前,synchronized的实现确实直接关联到操作系统层面的重量级锁(Heavyweight Lock),每次锁的获取和释放都涉及用户态到内核态的切换,性能开销很大。这正是其“性能差”名声的由来。然而,现代JVM(以HotSpot为例)已经实现了彻底的优化,其核心思想是:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。因此,完全没有必要一开始就动用开销巨大的操作系统互斥量。这就是锁升级(锁膨胀)策略的出发点,而这一切都围绕Synchronized底层Monitor机制展开。对于鳄鱼Java社区的开发者而言,理解这一机制是进行高性能并发编程和复杂问题诊断的基石。
二、基石:对象头与Mark Word
要理解锁的状态如何存储,必须先了解Java对象的内存布局。一个普通对象在堆中的结构包括:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。其中,对象头是理解锁状态的关键,它主要包含两部分:
1. **Mark Word(标记字)**: 存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等。在64位JVM中,它通常占64位(8字节)。
2. **Klass Pointer(类型指针)**: 指向对象元数据(即它的类)的指针。
Mark Word是锁状态的“记录本”。为了在极小的空间内存储大量信息,它的位模式会根据对象的状态(无锁、偏向锁、轻量级锁、重量级锁、GC标记)而动态复用。下图展示了HotSpot虚拟机中64位Mark Word的典型存储布局:
|-----------------------------------------------------------------------------------------------------------| | Mark Word (64 bits) | |-----------------------------------------------------------------------------------------------------------| | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | State: (tag) | |-----------------------------------------------------------------------------------------------------------| | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | State: Biased | |-----------------------------------------------------------------------------------------------------------| | ptr_to_lock_record:62 | lock:2 | State: Lightweight Lock | |-----------------------------------------------------------------------------------------------------------| | ptr_to_heavyweight_monitor:62 | lock:2 | State: Heavyweight Lock | |-----------------------------------------------------------------------------------------------------------| | | lock:2 | State: Marked for GC | |-----------------------------------------------------------------------------------------------------------|
其中,lock:2(最后2位)是锁标志位,它直接决定了如何解读Mark Word的其他位:
- **01**: 无锁或偏向锁(此时看biased_lock:1位,0为无锁,1为偏向锁)。
- **00**: 轻量级锁。
- **10**: 重量级锁。
- **11**: GC标记。
这个精巧的设计使得Synchronized底层Monitor机制的状态信息得以高效地内嵌在每一个Java对象中,无需额外的庞大结构,这是锁能够快速升级和降级的基础。
三、核心模型:Monitor对象与三队列
当锁升级为重量级锁时,Mark Word中存储的指针(ptr_to_heavyweight_monitor)指向的是一个真正的Monitor对象(由C++实现,在HotSpot中称为ObjectMonitor)。这是传统意义上“管程”的完整实现,其内部管理着三个关键队列,是理解线程阻塞和唤醒的核心:
```c++ // ObjectMonitor 简化结构示意 class ObjectMonitor { void* volatile _object; // 关联的Java对象 void* volatile _owner; // 指向持有锁的线程(如Thread*) volatile int _count; // 锁的重入次数 ObjectWaiter* volatile _EntryList; // 等待锁的线程队列(入口队列) ObjectWaiter* volatile _WaitSet; // 调用了wait()的线程队列(等待集合) volatile int _recursions; // 重入计数 } ```
1. _owner(所有者): 指向当前持有该Monitor的线程。这是锁的核心标识。
2. _EntryList(入口队列/竞争队列): 当多个线程同时竞争锁时,没有抢到锁的线程不会立即被操作系统挂起(这很昂贵),而是被封装成ObjectWaiter对象,进入_EntryList中自旋等待一小段时间。如果在此期间锁被释放,它们能直接获取,避免切换;如果自旋后仍未获取,则被真正的操作系统挂起(进入阻塞状态)。
3. _WaitSet(等待集合): 当持有锁的线程调用Object.wait()方法时,它会释放锁,并将自身线程封装成ObjectWaiter加入_WaitSet,然后进入等待状态。当其他线程调用Object.notify()/notifyAll()时,相应的线程会从_WaitSet移出,并重新尝试进入_EntryList去竞争锁。
这个“一主两队列”的模型清晰地划分了线程的几种状态:运行(owner)、就绪/阻塞(EntryList)、条件等待(WaitSet)。整个Synchronized底层Monitor机制在重量级状态下,就表现为对这个ObjectMonitor对象及其队列的精确操作。
四、锁升级全景图:从偏向到重量级的智慧
现在,我们将对象头(Mark Word)和Monitor对象串联起来,看一次完整的锁升级流程。这是Synchronized底层Monitor机制最精妙的部分。
第一步:无锁态 (01)
一个新创建的对象,默认处于无锁状态。其Mark Word存储的是哈希码、分代年龄等信息,锁标志位为01。
第二步:偏向锁 (Biased Locking)
这是“乐观假设”的体现。当第一个线程T1来获取锁时,JVM会通过CAS操作,将Mark Word的锁标志位更新为偏向模式(01,且biased_lock=1),并记录下T1的线程ID。之后,只要T1再次进入这个同步块,它甚至不需要执行任何同步指令(如CAS),只需要检查Mark Word中的线程ID是否是自己即可,开销极低。
偏向锁的撤销: 一旦有第二个线程T2来竞争这个锁,偏向模式就宣告结束。JVM需要在某个安全点(SafePoint)暂停持有偏向锁的线程T1,检查它是否已退出同步块。如果已退出,则将锁置为无锁态(01,biased_lock=0),然后T1和T2公平竞争;如果仍在执行,则升级为轻量级锁。
第三步:轻量级锁 (Lightweight Locking)
当锁撤销偏向或存在轻微竞争时,JVM会采用轻量级锁。线程会在自己的栈帧中创建一个名为锁记录(Lock Record)的空间。然后通过CAS操作,尝试将对象头的Mark Word复制到自己的锁记录中,并同时将对象头的Mark Word更新为指向这个锁记录的指针。如果成功,线程获得锁,锁标志位变为00。
此时,锁的获取仍是在用户态通过CAS自旋完成,避免了内核切换。如果另一个线程竞争失败,它也会在自己的栈上创建锁记录并自旋等待。
第四步:重量级锁 (Heavyweight Locking)
当轻量级锁的竞争加剧(比如自旋超过一定次数,或自旋的线程数超过CPU核数的一半),锁就会膨胀为重量级锁。此时,JVM会分配并初始化一个ObjectMonitor对象,将对象头的Mark Word更新为指向该Monitor的指针,锁标志位变为10。后续所有未获得锁的线程,不再自旋,而是进入ObjectMonitor的_EntryList中,由操作系统进行真正的挂起和唤醒调度。
这个升级路径是单向的:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。这个过程完美体现了“按需分配开销”的工程思想,使得synchronized在低竞争场景下性能堪比无锁编程,在高竞争下又能提供稳定的互斥保障。
五、Java生态的体现与实战启示
理解Synchronized底层Monitor机制,对于解决实际问题和进行系统调优有直接帮助:
1. 性能调优:
- 如果明确知道同步块存在高竞争(如秒杀核心逻辑),可以添加JVM参数-XX:-UseBiasedLocking关闭偏向锁,直接使用轻量级锁,避免无谓的偏向锁撤销开销。
- 调整自旋参数:-XX:PreBlockSpin(JDK 1.6已废弃,由JVM自适应自旋替代),但在特定场景下了解其原理有助于分析。
2. 问题诊断:
- 使用jstack命令查看线程dump时,线程状态为“BLOCKED (on object monitor)”通常意味着在等待进入synchronized块,即位于ObjectMonitor的_EntryList中;状态为“WAITING (on object monitor)”则意味着调用了wait(),位于_WaitSet中。
- 在鳄鱼Java社区的故障排查案例中,曾有一个因不当使用大对象作为锁,导致频繁的锁升级和降级(伴随Mark Word的拷贝与CAS),造成性能波动的典型问题。理解Monitor机制后,将其改为一个专用的Object lock = new Object(),问题迎刃而解。
3. 编码启示:
- 锁的粒度要小,持有时间要短,这不仅是编码规范,更是为了减少锁竞争,让锁尽量停留在低开销的偏向锁或轻量级锁状态。
- 理解wait()、notify()与Monitor的_WaitSet队列的关系,是正确使用线程间协作的基础。
总结与思考
Synchronized底层Monitor机制是Java并发基石中一件精雕细琢的艺术品。它从一个小小的对象头Mark Word出发,通过锁升级策略,智能地应对从单线程重复访问到多线程激烈竞争的各种场景,在简易的API背后隐藏着复杂的自适应优化逻辑。
现在,请你思考:在大量使用synchronized的历史遗留系统中,如何通过分析对象头的锁状态来定位潜在的性能热点?随着协程(Virtual Threads)在Project Loom中的成熟,这种基于操作系统线程挂起/唤醒的重量级锁Monitor模型,会如何演变以适应更轻量级的并发单元?当你在鳄鱼Java社区设计一个极高并发的中间件时,除了使用synchronized,能否借鉴其自适应升级的思想,设计出针对自身业务场景的、更细粒度的并发控制策略?对这些问题的探索,将使你不仅是一个API的使用者,更成为一名洞察本质的系统设计师。
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





