synchronized最终通过objectmonitor封装调用futex系统调用实现线程阻塞与唤醒,而非直接挂钩pthread_mutex_t;其在linux下底层依赖futex机制,在自旋失败后触发用户态到内核态切换,由os完成线程挂起和唤醒。
synchronized 最终确实会落到 linux 的 pthread_mutex_t 上,但不是直接调用,而是通过 jvm 一层层封装后,交由操作系统内核完成线程阻塞与唤醒——这个过程涉及用户态、内核态切换和 futex 机制,不是简单“挂钩”。
对象锁如何触发 pthread_mutex
当 synchronized 进入重量级锁阶段(即发生锁竞争且自旋失败),JVM 会为该对象分配一个 C++ 层的 ObjectMonitor 实例。这个 monitor 内部持有一个平台相关的互斥体(os::PlatformMonitor),在 Linux 下它底层封装的就是 pthread_mutex_t:
- JVM 启动时会初始化 pthread 库,并在需要时调用
pthread_mutex_init()创建互斥体 - 线程调用
monitorenter时,若发现 monitor 已被占用,就执行pthread_mutex_lock() - 解锁时调用
pthread_mutex_unlock(),并可能唤醒等待队列中的线程
futex 是实际执行的关键
现代 JVM(如 HotSpot)并不直接使用裸的 pthread_mutex_t,而是基于 Linux 的 futex(Fast Userspace muTEX)系统调用构建:
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 轻量竞争时,JVM 先在用户态用 CAS 检查锁状态,避免陷入内核
- 只有真正需要阻塞时,才调用
sys_futex(uaddr, FUTEX_WAIT, ...)让线程挂起 - 释放锁时,若存在等待者,触发
sys_futex(uaddr, FUTEX_WAKE, ...)唤醒 -
pthread_mutex_t本身在 glibc 中就是用 futex 实现的,所以 JVM 绕过 glibc 直接调用 futex 更高效
为什么不是每个 synchronized 都走 pthread
因为 JVM 做了多级优化,绝大多数场景根本不会走到 pthread 层:
- 无竞争时:偏向锁或轻量级锁,纯用户态 CAS + 自旋,不涉及任何系统调用
- 短暂竞争时:自适应自旋,仍在用户态重试,避免上下文切换开销
- 仅当自旋失败、线程必须休眠时,才升级为重量级锁,进而触发 futex/pthread 流程
线程状态变化与内核调度联动
一旦进入 futex 等待,线程状态从 RUNNABLE 变为 BLOCKED,背后是:
- 内核将线程从 CPU 调度队列移出,放入该 futex 地址对应的等待链表
- 线程让出时间片,不再消耗 CPU,直到被
FUTEX_WAKE显式唤醒 - 唤醒后重新参与调度,但需再次竞争锁(CAS 或 pthread_mutex_trylock)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










