synchronized底层通过monitorenter和monitorexit指令操作对象头中的monitor实现加锁;monitor逻辑上属于对象实例(或Class对象),存储于Mark Word,支持偏向锁、轻量级锁和重量级锁三级优化,且要求同步对象非null,否则抛NullPointerException,同时依靠entry count计数器实现可重入。

synchronized 同步代码块的底层加锁,本质是通过 monitorenter 和 monitorexit 这两条 JVM 字节码指令实现的。它并不直接“对变量加锁”,而是对**对象实例(或 Class 对象)的监视器(monitor)**进行获取与释放。
monitor 是什么?不是锁在变量上,而是在对象头里
JVM 中每个 Java 对象在堆中都有一个对应的 monitor 结构(逻辑上),实际存储在对象头(Object Header)的 Mark Word 中。当执行 synchronized(obj) 时,JVM 尝试获取的是 obj 所关联的那个 monitor,而不是给变量 obj 本身加锁——变量只是引用,真正被锁定的是它指向的堆对象。
如果该 monitor 当前未被占用,线程成功获取并进入临界区;若已被其他线程持有,当前线程会阻塞,进入该 monitor 的 EntryList 等待唤醒。
monitorenter 指令如何工作?三步完成轻量级加锁尝试
monitorenter 并非一步到位地挂起线程,而是按优先级尝试三种锁机制:
- 偏向锁(Biased Locking):若对象未被锁且处于可偏向状态,JVM 直接将线程 ID 写入对象头 Mark Word,不进内核态,无同步开销
- 轻量级锁(Lightweight Locking):若偏向锁已失效(如发生竞争),线程在栈帧中创建锁记录(Lock Record),用 CAS 尝试将对象头指向该记录;成功则进入临界区
- 重量级锁(Heavyweight Locking):CAS 失败说明存在多线程竞争,JVM 膨胀 monitor,把线程挂起并放入操作系统 Mutex 队列,进入阻塞态
加锁对象必须是非 null,否则抛出 NullPointerException
synchronized(obj) 编译后生成 monitorenter 指令,但 JVM 在执行该指令前会先校验 obj 是否为 null。一旦为 null,不会等到加锁失败,而是立即抛出 NullPointerException。所以务必确保同步对象已初始化。
常见错误示例:
Object lock = null;
synchronized(lock) { ... } // 运行时报 NPE,不是 IllegalMonitorStateException
monitor 的释放由 monitorexit 保证,且支持重入
每个 monitorenter 必须对应一个配对的 monitorexit(编译器自动插入,包括异常路径)。monitor 内部维护一个计数器(entry count)和持有线程字段:
- 同一线程每进入一次,计数器 +1;每退出一次,-1
- 只有计数器归零时,monitor 才真正释放,其他线程才可能获取
- 这正是 synchronized 可重入(reentrant)的底层依据










