CodeStats资深底层技术爱好者与实战派架构师WWAIC全周 AI 编程范式创始人专注计算机体系结构、操作系统内核、Java 虚拟机实现原理与并发编程底层。本文手撕 Java 所有锁的底层不讲空话从字节码 → 对象头 → AQS → 读写锁分离 → 邮戳锁乐观读彻底讲透每种锁在操作系统指令层到底干了什么以及为什么它们的效率有巨大差异。 参考原文CSDN 深入CPU与操作系统的底层骗局彻底吃透程序运行本质从CPU权限控制看懂Linux、Windows、鸿蒙的本质区别Java一个volatile变量写进去CPU和内存发生了什么——从MESI到LOCK前缀的硬件之旅 目录点击跳转 核心观点一句话一、synchronized从字节码到重量级锁的完整进化二、ReentrantLockAQS 的 CLH 队列 可重入 超时机制三、ReentrantReadWriteLock读锁共享、写锁互斥的底层实现四、StampedLock乐观读 邮戳锁的无锁思想五、全锁对比表可重入、超时、读写分离、乐观读的本质差异六、两个核心问题操作系统指令层深度对比6.1 AQS 锁与重量级锁有何本质区别效率差异的指令根源6.2 读锁写锁、邮戳锁为什么在某些场景下更快CPU 缓存与内存屏障的作用七、操作系统调度与锁队列从用户态自旋到内核态挂起的完整图谱八、总结Java 所有锁的统一本质——操作系统 mutex 等待队列的变体 最后点赞・收藏・评论 核心观点一句话Java 中所有的锁底层都是操作系统提供的互斥量mutex加上等待队列wait queue的变体。不同锁的性能差异本质上是对用户态自旋、队列管理、读写分离、乐观读等策略的取舍最终都会在某个时刻调用futex_wait或futex_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计数器加一。二、ReentrantLockAQS 的 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如果没有写锁writeCount0则 CAS 增加读计数成功即获取读锁。重入每个线程持有读锁的次数记录在ThreadLocal的HoldCounter中。等待队列读锁线程共享同一个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 (重量级)ObjectMonitorJVM 用户态 (_EntryList_cxq) 内核 futex✅❌❌❌阻塞时立即 futex_waitReentrantLockAQS用户态 CLH 队列 park/unpark✅✅❌❌自旋失败后 parkReentrantReadWriteLockAQS共享/独占节点混合队列✅ (读锁 per-thread)✅ (写锁支持)✅❌读锁/写锁自旋失败后 parkStampedLock自定义 state写锁用 CLH 队列读锁用简单计数❌✅✅✅写锁和读锁非乐观可能 park六、两个核心问题操作系统指令层深度对比6.1 AQS 锁与重量级锁有何本质区别效率差异的指令根源操作重量级锁 (synchronized 膨胀后)AQS 锁 (ReentrantLock)无竞争加锁CAS 修改 Mark WordCAS 修改 state轻度竞争有限自旋膨胀较快持续自旋 入 CLH 队列后再次自旋线程阻塞调用futex_wait系统调用调用LockSupport.park()底层也是futex_wait但更晚触发唤醒futex_wakeunpark公平性控制只有非公平可配置公平/非公平效率差异的根源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_waitfutex 等待队列ReentrantLockAQS CLH 队列LockSupport.park()→futex_waitfutex 等待队列ReentrantReadWriteLock共享/独占节点同上同上StampedLock (写锁)简单 CLH非完全 AQSU.park()→futex_wait同上关键结论无论哪种锁最终阻塞的线程都会进入内核的futex等待队列。区别在于进入内核前的“挣扎”程度。八、总结Java 所有锁的统一本质——操作系统 mutex 等待队列的变体从synchronized到StampedLock可以画出一条完整的技术进化线无锁 → 偏向锁 → 轻量级锁 → 重量级锁 (ObjectMonitor) → AQS (CLH 自旋) → 读写锁分离 (共享模式) → 邮戳锁 (乐观读 不可重入)所有锁的共同底层互斥依赖 CPU 的CAS指令cmpxchg或xchg实现原子性。阻塞/唤醒最终调用 Linuxfutex系统调用线程挂入内核等待队列。等待队列JVM 在用户态维护自己的队列_EntryList或 CLH减少内核切换。性能差异的本质更多自旋 → 更少系统调用 → 更高性能但浪费 CPU。读写分离 → 提高读并发 → 减少写锁阻塞。乐观读 → 完全无锁 → 极致读性能。 最后点赞・收藏・评论如果这篇文章帮你彻底搞懂了 Java 全系列锁的底层原理 点赞让更多同学看到这篇硬核文章⭐ 收藏方便以后随时复习完整逻辑链 评论留下你的疑问或补充从字节码到 futex从偏向锁到邮戳锁你已掌握 Java 并发的全部底层。© 2026 CodeStats原创不易转载注明出处