synchronized 方法能同步是因为编译器自动插入 monitorenter/monitorexit 指令,触发 ObjectMonitor 的获取与释放;ObjectMonitor 是 C++ 实现的结构体,含 _owner、_EntryList、_WaitSet 等字段,支持重入和等待队列;锁升级为偏向→轻量级→重量级,JDK 15+ 默认关闭偏向锁。

Java 中 synchronized 不是靠 JVM 解释执行的“语法糖”,而是直接绑定到操作系统级的互斥原语,底层依赖的是 monitorenter / monitorexit 字节码指令 + HotSpot 的 ObjectMonitor 机制。
为什么 synchronized 方法没有显式加锁却能同步?
因为编译器会把 synchronized 方法自动转换为在方法入口插入 monitorenter 指令、出口插入 monitorexit 指令。JVM 执行时,这两条指令会触发对当前对象(或 class 对象)关联的 ObjectMonitor 的获取与释放。
关键点:
-
ObjectMonitor是 C++ 实现的,每个 Java 对象头(mark word)里存着指向它的指针(偏向锁被撤销后) - 非静态方法锁的是
this对象;静态方法锁的是Class对象(即MyClass.class) - 如果方法抛异常,JVM 仍会保证
monitorexit执行 —— 这就是 synchronized 不需要手动 try-finally 的原因
锁升级过程:偏向锁 → 轻量级锁 → 重量级锁
HotSpot 默认开启锁优化,实际锁状态在对象头 mark word 中动态变化,不是固定一种实现:
立即学习“Java免费学习笔记(深入)”;
- 偏向锁:假设无竞争,直接把线程 ID 记录在 mark word 中;后续同一线程重入无需 CAS
-
轻量级锁:有竞争但不激烈时,用 CAS 尝试将对象头指向线程栈中的
Lock Record;失败则升级 -
重量级锁:真正调用操作系统 mutex,挂起线程(
pthread_mutex_lock),此时 mark word 存的是ObjectMonitor*地址
锁升级不可逆(但 JDK 10+ 引入了锁降级尝试,仅限 GC 阶段,生产环境基本不生效)。
ObjectMonitor 里到底存了什么?
这是真正干活的结构体,核心字段包括:
struct ObjectMonitor {
volatile intptr_t _owner; // 持有锁的线程(或递归计数)
volatile intptr_t _next_owener; // 下一个可能获得锁的线程(避免唤醒后立即竞争)
ParkEvent * _WaitSet; // wait() 队列(EntryList 里的线程调用 wait 后转入)
ParkEvent * _EntryList; // 等待获取锁的线程队列(阻塞中)
Thread * _succ; // 唤醒后即将运行的线程(避免虚假唤醒)
};
注意:_owner 不是简单存 thread ID,而是存 Thread* 指针(64 位下压缩后也占 8 字节),所以可支持重入判断;_WaitSet 和 _EntryList 都是通过 ParkEvent(封装了 pthread_cond 和 pthread_mutex)实现的。
常见误区和调试线索
面试常被问但容易答偏的点:
-
synchronized(this)和synchronized(任意对象)效果一致,只要对象没逃逸、没被共享到别的锁逻辑里 - 锁的是对象“监视器”(monitor),不是对象本身 —— 所以
new Object()每次都新建 monitor,无法互斥 - 用 jstack 看线程状态为
BLOCKED,说明在_EntryList里排队;状态为WAITING(如java.lang.Object.wait()),说明在_WaitSet里 - JDK 15+ 默认关闭偏向锁(
-XX:+UseBiasedLocking已废弃),轻量级锁成为默认起点
真正难的不是记住流程,而是理解:所有优化(偏向、自旋、队列分离)都是为了在「无竞争」和「短暂竞争」场景下,尽量避免陷入操作系统内核态 —— 一旦调用 pthread_mutex_lock,就至少多一次用户态/内核态切换,成本陡增。










