锁膨胀是JVM将synchronized锁从偏向锁→轻量级锁→重量级锁单向升级的过程;它本身不直接挂起线程,而是触发ObjectMonitor创建,后续竞争失败时才通过OS原语(如futex)挂起线程。

锁膨胀本身不直接“通过操作系统内核挂起线程变量”,而是触发 ObjectMonitor 的创建与绑定,之后所有锁竞争行为(获取失败、释放唤醒)才真正交由操作系统内核介入——挂起和唤醒线程的动作,是 ObjectMonitor 在重量级状态下调用系统原语(如 pthread_mutex 或 futex)时发生的。
锁膨胀只是重量级锁的“启动开关”
对象初始处于无锁或偏向/轻量级状态,此时锁操作完全在用户态完成(CAS、自旋等),不涉及内核。只有当多个线程反复竞争失败,JVM 判定“靠自旋抢不到”,才会执行膨胀流程:
- 从全局池分配一个 native 的
ObjectMonitor实例 - 将对象头的 mark word 改写为指向该 monitor 的指针
- 后续所有
synchronized进入/退出,都走 monitor 的入口逻辑
此时,线程是否被挂起,已不由 JVM 控制,而取决于 monitor 内部调度策略与底层 OS 的协作方式。
ObjectMonitor 如何把线程交给操作系统
一旦进入重量级状态,ObjectMonitor 就成为线程阻塞与唤醒的中枢。它本身不实现挂起,而是封装并调用操作系统提供的同步原语:
- _EntryList 中等待的线程,在尝试获取锁失败后,会调用
park()—— JVM 层最终映射为pthread_cond_wait()或 Linux futex 的 WAIT 操作 -
park()导致线程从用户态陷入内核态,由 OS 调度器将其标记为 TASK_INTERRUPTIBLE 状态,并移出 CPU 运行队列 - 持有锁的线程执行
monitorexit时,monitor 会从 _EntryList 挑选一个线程,调用unpark()—— 对应 OS 的pthread_cond_signal()或 futex WAKE - 被唤醒线程需重新参与 CPU 调度,恢复寄存器上下文,再次尝试获取 monitor 所有权
挂起不是针对“变量”,而是整个线程实体
注意:操作系统挂起的是线程(task_struct),不是某个“线程变量”。所谓“挂起线程变量”是一种误读。ObjectMonitor 中的字段(如 _owner、_cxq)只是记录状态的元数据,它们本身不会被挂起;真正被暂停执行的是调用 park() 的那个 Java 线程所对应的 OS 线程。
- 线程挂起时,其栈、寄存器、TLS 数据全部由 OS 保存
- ObjectMonitor 只负责维护谁在等、谁在睡、谁刚醒这些逻辑关系
- 内核并不知道 Java 对象或 monitor 结构,它只响应标准的同步系统调用
不公平性也源于操作系统调度
重量级锁默认是非公平的,这不是 JVM 主动设计的策略,而是继承自底层 OS 互斥机制的行为特征:
- OS 唤醒等待线程时,通常不保证 FIFO,可能跳过 _EntryList 头部而选中刚到达的 _cxq 中的线程
- 被唤醒的线程仍要重新竞争锁,若此时有新线程到来,可能“插队成功”
- 这种非确定性由内核调度器和 futex 实现细节共同决定,JVM 层难以干预











