摘要

synchronized 的底层并不仅仅停留在字节码和对象头,它最终依赖操作系统的互斥量(mutex)来实现线程的阻塞与唤醒。本文将详细解析 synchronized 与 OS 层互斥机制的联系,揭示从 JVM 到内核的完整锁实现链路。


一、引言

我们在前一篇文章中,已经从 字节码与 Monitor 的角度理解了 synchronized
但有一个关键问题还没有解答:

? monitorentermonitorexit 是 JVM 指令,那它们最终如何落实到硬件层面?

答案就是:依赖操作系统提供的互斥量(mutex)机制

也就是说,synchronized 从源码到运行的路径是:

Java 代码 → JVM 字节码 → Monitor → OS 互斥量 → CPU 硬件原子指令


二、为什么需要操作系统的支持?

在单核 CPU 时代,我们或许只需要简单的“关闭中断”就能实现互斥。但在现代多核 CPU + 多线程并发场景下,仅靠 JVM 内部逻辑无法解决所有问题:

  • 上下文切换:当线程无法获得锁时,需要进入阻塞状态并等待调度。
  • 线程唤醒:锁释放时,需要操作系统将等待的线程重新唤醒。
  • 跨 CPU 同步:不同 CPU 核心需要通过总线锁、缓存一致性协议来保证正确性。

这些操作超出了 JVM 的能力范围,因此必须依赖 操作系统内核提供的互斥原语


三、JVM Monitor 与操作系统互斥量

在 HotSpot 虚拟机中,synchronized 的实现依赖于 ObjectMonitor

当线程竞争激烈、轻量级优化失效时,ObjectMonitor 会退化为依赖操作系统的 mutex/condition 机制

Linux 平台 为例,HotSpot 中 ObjectMonitor 的底层实现依赖:

  • pthread_mutex_t(互斥量):保证只有一个线程能进入临界区。
  • pthread_cond_t(条件变量):配合 wait/notify 实现线程挂起与唤醒。

换句话说:

  • monitorenter → 对应 pthread_mutex_lock()
  • monitorexit → 对应 pthread_mutex_unlock()
  • Object.wait() / Object.notify() → 对应 pthread_cond_wait() / pthread_cond_signal()

四、加锁路径解析

我们用一个流程来梳理 synchronized 与 OS mutex 的关系:

1. 快速路径:用户态自旋

  • 首先尝试 CAS(Compare-And-Swap) 修改对象头的 Mark Word。
  • 如果成功,则不涉及操作系统调用(用户态完成加锁)。

2. 失败路径:进入阻塞

  • 如果 CAS 多次失败(说明竞争激烈),JVM 会将线程挂起。
  • 这时会调用 pthread_mutex_lock,线程进入 内核态,由操作系统接管调度。
  • 等待唤醒后,再次尝试获取锁。

3. 解锁路径

  • monitorexit 时,如果有线程等待,则通过 pthread_mutex_unlock + pthread_cond_signal 唤醒。
  • 唤醒的线程由操作系统调度,重新进入就绪队列。

这就是 用户态 → 内核态 的锁获取完整链路。


五、内核态切换的开销

调用操作系统互斥量意味着发生 用户态 → 内核态切换,其开销比单纯的 CAS 要大很多。

  • CAS 自旋:几十纳秒级别。
  • 内核阻塞/唤醒:可能需要微秒甚至毫秒。

因此 JVM 在设计 synchronized 时,采取了 锁优化策略

  1. 偏向锁 —— 在无竞争时,几乎零开销。
  2. 轻量级锁 —— 通过 CAS 和自旋解决短时间竞争。
  3. 重量级锁 —— 竞争严重时,才退化为依赖 OS mutex。

这种分层机制,最大化地减少了进入内核态的次数。


六、与 CPU 指令的联系

操作系统的互斥量,最终也是依赖 CPU 的原子指令实现的,比如:

  • x86 架构lock cmpxchgxchg
  • ARM 架构LDREX/STREX

这些硬件指令能在总线上加锁,保证多核 CPU 下的原子性。

所以完整链路是:

synchronized → JVM Monitor → pthread_mutex → 内核调度 → CPU 原子指令


七、直观流程图

我们用 Mermaid 图画出 synchronized 与 OS 互斥量的联系:

image.png


八、总结

  1. synchronized 底层依赖 JVM Monitor,而 Monitor 在竞争激烈时会调用操作系统的 互斥量(mutex)
  2. monitorenter/monitorexit → 映射到 pthread_mutex_lock/unlock,保证线程互斥。
  3. wait/notify → 对应 pthread_cond_wait/signal,实现线程等待和唤醒。
  4. 为减少内核切换开销,JVM 采用 偏向锁、轻量级锁、重量级锁 分层优化。
  5. 最终所有锁的实现都依赖 CPU 原子指令 来保障并发正确性。
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]