CodeStats|资深底层技术爱好者与实战派架构师,WWAIC(全周 AI 编程)范式创始人
专注计算机体系结构、操作系统内核、Java 虚拟机实现原理与并发编程底层。
本文手撕 Java 所有锁的底层:不讲空话,从 字节码 → 对象头 → AQS → 读写锁分离 → 邮戳锁乐观读,彻底讲透 每种锁在操作系统指令层到底干了什么,以及为什么它们的效率有巨大差异


📚 参考原文(CSDN )


📑 目录(点击跳转)


💡 核心观点一句话

Java 中所有的锁,底层都是操作系统提供的互斥量(mutex)加上等待队列(wait queue)的变体。
不同锁的性能差异,本质上是对 用户态自旋、队列管理、读写分离、乐观读 等策略的取舍,最终都会在某个时刻调用 futex_waitfutex_wake 进入内核。


一、synchronized:从字节码到重量级锁的完整进化

1.1 字节码指令与对象头

synchronized 编译后生成 monitorenter / monitorexit。每个 Java 对象头中的 Mark Word 存储锁状态。

1.2 锁升级(JDK 1.6+)

  • 偏向锁:单线程,Mark Word 存线程 ID,无需 CAS。
  • 轻量级锁:少量竞争,CAS 自旋修改 Mark Word,用户态。
  • 重量级锁:高竞争,Mark Word 指向 ObjectMonitor,内含 _EntryList_WaitSet_cxq 队列,最终调用 futex 进入内核。

可重入原理_owner 记录当前线程,_recursions 计数器加一。


二、ReentrantLock:AQS 的 CLH 队列 + 可重入 + 超时机制

2.1 AQS 核心结构

AbstractQueuedSynchronizer 维护一个 volatile int state 表示锁状态,以及一个 CLH 双向队列(FIFO)存储等待线程。

  • state = 0:未锁定
  • state = 1(可重入时 >1):锁定,且 exclusiveOwnerThread 记录持有线程。

2.2 可重入实现

tryAcquire 中判断当前线程是否为 owner,是则 state++

2.3 超时机制

tryAcquireNanos 调用 doAcquireNanos,在自旋过程中检查 System.nanoTime(),超时后返回 false,不再进入阻塞队列。底层仍通过 LockSupport.parkNanos 实现,最终调用 futex_wait 带超时参数

2.4 公平与非公平

  • 非公平:新线程直接 CAS 抢锁,抢不到才入队。
  • 公平:先检查队列中是否有等待者,有则直接入队。

三、ReentrantReadWriteLock:读锁共享、写锁互斥的底层实现

3.1 状态拆分

AQS 的 state 被分为 高 16 位存读锁计数低 16 位存写锁计数

3.2 读锁实现(共享模式)

  • tryAcquireShared:如果没有写锁(writeCount==0),则 CAS 增加读计数,成功即获取读锁。
  • 重入:每个线程持有读锁的次数记录在 ThreadLocalHoldCounter 中。
  • 等待队列:读锁线程共享同一个 SHARED 节点,被唤醒后全部尝试重入。

3.3 写锁实现(独占模式)

ReentrantLock 类似,但必须等读锁计数为 0 才能获取。

3.4 锁降级

持有写锁后可以再获取读锁,然后释放写锁,变成读锁。底层允许是因为 state 中写计数减少的同时读计数增加,没有竞争问题。


四、StampedLock:乐观读 + 邮戳锁的无锁思想

4.1 三种访问模式

  • 写锁writeLock() 返回邮戳,独占,类似写锁。
  • 读锁readLock() 阻塞写锁,但允许多个读锁共存。
  • 乐观读tryOptimisticRead() 不阻塞写锁,仅获取一个版本戳(stamp)。后续 validate(stamp) 检查是否有写操作发生过。

4.2 底层原理

StampedLock 内部维护一个 long state,其中包含 写锁计数 + 读锁计数 + 版本戳(一个简单的计数器)。乐观读只是读取 state 的快照,不涉及任何 CAS 或队列。验证时比较当前 state 与快照,若不同则说明期间发生了写锁获取,需重试。

4.3 为什么性能极高

乐观读 完全无锁,不修改任何共享变量,因此不触发缓存一致性协议,也不产生内存屏障。仅在验证失败时才升级为读锁。适合读多写少的场景。

4.4 不可重入

StampedLock 不支持重入,否则容易死锁。官方设计如此。


五、全锁对比表:可重入、超时、读写分离、乐观读的本质差异

锁类型 底层同步器 等待队列管理 可重入 超时支持 读写分离 乐观读 系统调用触发点
synchronized (重量级) ObjectMonitor JVM 用户态 (_EntryList+_cxq) + 内核 futex 阻塞时立即 futex_wait
ReentrantLock AQS 用户态 CLH 队列 + park/unpark 自旋失败后 park
ReentrantReadWriteLock AQS 共享/独占节点混合队列 ✅ (读锁 per-thread) ✅ (写锁支持) 读锁/写锁自旋失败后 park
StampedLock 自定义 state 写锁用 CLH 队列,读锁用简单计数 写锁和读锁(非乐观)可能 park

六、两个核心问题(操作系统指令层深度对比)

6.1 AQS 锁与重量级锁有何本质区别?效率差异的指令根源

操作 重量级锁 (synchronized 膨胀后) AQS 锁 (ReentrantLock)
无竞争加锁 CAS 修改 Mark Word CAS 修改 state
轻度竞争 有限自旋,膨胀较快 持续自旋 + 入 CLH 队列后再次自旋
线程阻塞 调用 futex_wait 系统调用 调用 LockSupport.park()(底层也是 futex_wait,但更晚触发)
唤醒 futex_wake unpark
公平性控制 只有非公平 可配置公平/非公平

效率差异的根源:AQS 在用户态做了更多自旋和队列管理,推迟了进入内核的时间。重量级锁的膨胀策略更激进,一旦自旋失败次数超过阈值(默认 10 次),立即进入内核。因此在高竞争短临界区场景,AQS 胜出;在极高竞争长临界区,两者差异缩小。

6.2 读锁写锁、邮戳锁为什么在某些场景下更快?CPU 缓存与内存屏障的作用

  • 读写锁分离:读锁之间不互斥,多个读线程可以同时持有。底层依赖 AQS 的共享模式,多个读线程在 CLH 队列中可以被同时唤醒。这减少了线程上下文切换。
  • StampedLock 乐观读:乐观读不修改任何共享变量,因此 不会触发 CPU 缓存一致性协议(MESI)的写无效广播,也不产生内存屏障(如 StoreLoad 等)。在多核 CPU 下,这避免了昂贵的缓存同步开销。验证时只需一次 volatile 读,代价极低。

指令层差异

  • 写锁/互斥锁:lock cmpxchg (CAS) 或 xchg 指令,触发缓存行失效。
  • 乐观读:仅 mov 读取 volatile 变量,加上 lfence(某些平台)保证顺序性,无写操作。

七、操作系统调度与锁队列:从用户态自旋到内核态挂起的完整图谱

锁类型 用户态队列 何时进入内核 内核队列
synchronized 重量级 _cxq + _EntryList 自旋失败后,调用 ParkEvent::park()futex_wait futex 等待队列
ReentrantLock AQS CLH 队列 LockSupport.park()futex_wait futex 等待队列
ReentrantReadWriteLock 共享/独占节点 同上 同上
StampedLock (写锁) 简单 CLH(非完全 AQS) U.park()futex_wait 同上

关键结论:无论哪种锁,最终阻塞的线程都会进入内核的 futex 等待队列。区别在于 进入内核前的“挣扎”程度


八、总结:Java 所有锁的统一本质——操作系统 mutex + 等待队列的变体

synchronizedStampedLock,可以画出一条完整的技术进化线:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁 (ObjectMonitor) → AQS (CLH + 自旋) → 读写锁分离 (共享模式) → 邮戳锁 (乐观读 + 不可重入)

所有锁的共同底层

  • 互斥:依赖 CPU 的 CAS 指令(cmpxchg)或 xchg 实现原子性。
  • 阻塞/唤醒:最终调用 Linux futex 系统调用,线程挂入内核等待队列。
  • 等待队列:JVM 在用户态维护自己的队列(_EntryList 或 CLH),减少内核切换。

性能差异的本质

  • 更多自旋 → 更少系统调用 → 更高性能(但浪费 CPU)。
  • 读写分离 → 提高读并发 → 减少写锁阻塞。
  • 乐观读 → 完全无锁 → 极致读性能。

🔚 最后:点赞・收藏・评论

如果这篇文章帮你彻底搞懂了 Java 全系列锁的底层原理

  • 👍 点赞 让更多同学看到这篇硬核文章
  • ⭐ 收藏 方便以后随时复习完整逻辑链
  • 💬 评论 留下你的疑问或补充

从字节码到 futex,从偏向锁到邮戳锁,你已掌握 Java 并发的全部底层。


© 2026 CodeStats|原创不易,转载注明出处

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐