Skip to content

Java 的 synchronized 是怎么实现的? ​

  • 标签:Java并发、Java

🏛 业务架构师 ​

synchronized 的实现链条:字节码层是 monitorenter/monitorexit 指令(同步方法则是 ACC_SYNCHRONIZED 标志),每个对象关联一个 Monitor(监视器锁),底层映射到操作系统 mutex——重量级原因:加锁要陷入内核态、线程阻塞要上下文切换。

JDK 6 的锁升级优化是重点,它展示了 JVM 如何"替业务买单":

  • 偏向锁:只有一个线程访问时,对象头记录线程 ID,后续该线程进入零成本(无 CAS);
  • 轻量级锁:第二个线程来了,升级为 CAS 自旋竞争——短暂等待的场景下自旋比挂起便宜;
  • 重量级锁:自旋失败(竞争激烈或持锁太久),膨胀为 Monitor,未抢到的线程挂起排队。

业务视角的三点启示:

  1. 锁升级是自适应的成本优化:JVM 按实际竞争强度自动选择便宜的实现——这给了"先写正确的同步代码"底气:不要因为"怕锁慢"就绕开同步裸写,错误优先于性能;
  2. 锁优化的边界:同一锁高并发长持有,最终都会膨胀到重量级——热点锁的解法不在"锁的实现",在架构(减小粒度、分段、无锁结构、队列串行化);
  3. 轻量级锁自旋是烧 CPU 的:竞争激烈时大量自旋浪费 CPU(JDK 15 已默认废弃偏向锁,因为维护成本高于收益)——没有银弹的锁,只有按场景的权衡,这和业务架构"按瓶颈演进而非预设最优"是同一个思想。

一句话:synchronized 的演进史 = JVM 把锁的复杂度从业务代码手里接过去的历史;理解它,是为了把注意力留给真正的架构级并发设计。

🏗 系统架构师 ​

synchronized 基于对象监视器(Monitor),JDK 6 后是一套锁升级体系,我按"字节码 → 对象头 → 升级链"讲:

  • 字节码层:编译为 monitorenter / monitorexit 指令(同步方法则是 ACC_SYNCHRONIZED 标志),执行时关联对象的 Monitor;monitor 进入数不为 0 时其他线程阻塞;
  • 对象头 Mark Word:存锁状态(无锁 → 偏向 → 轻量级 → 重量级)、hash、GC 年龄,锁升级就是 Mark Word 内容的改写;
  • 锁升级路径:
    1. 偏向锁:只有一个线程反复获取,Mark Word 记录线程 ID,之后该线程进入零成本(CAS 都不用)。竞争出现(第二线程)即撤销偏向,升级;JDK 15 后默认废弃(维护成本高收益小);
    2. 轻量级锁:线程栈帧建 Lock Record,CAS 把 Mark Word 指向它;成功即持有,失败自旋重试——适合锁持有时间极短、竞争温和的场景;
    3. 重量级锁:自旋超限或第三方竞争,膨胀为 ObjectMonitor(底层 mutex),未抢到线程 park 挂起进 EntryList,monitorexit 唤醒——涉及用户态/内核态切换,成本高但让出 CPU,适合持锁久的场景;
  • 锁升级不可逆(只能升不能降)。

wait/notify 底座:ObjectMonitor 有 WaitSet,wait 释放锁进等待集,notify 迁移到 EntryList 竞争——这就是"wait/notify 必须在 synchronized 内"的原因。

工程启示:JVM 已把低竞争 synchronized 优化到接近无锁,日常不必为了性能回避它;要优化的真正方向是缩小临界区与减少共享。

💻 全栈开发专家 ​

从字节码到锁升级分层讲:

1. 字节码层:

  • 同步代码块:monitorenter / monitorexit 指令(退出要两条,异常路径一条);
  • 同步方法:ACC_SYNCHRONIZED 标志,方法调用时隐式检查。

2. 对象与监视器:每个对象关联一个 Monitor(ObjectMonitor,C++ 实现),内部有 _owner(持有线程)、_EntryList(阻塞队列)、_WaitSet(wait 的线程)。重量级锁就依赖它——阻塞要走操作系统 mutex(用户态↔内核态切换,贵)。

3. 锁升级(JDK 6 优化核心),信息记录在对象头 MarkWord:

text
无锁 → 偏向锁 → 轻量级锁 → 重量级锁(只升不降)
  • 偏向锁:只有一个线程反复获取,MarkWord 记下线程 ID,之后进入只比对 ID,几乎零成本。JDK 15 起废弃(维护复杂、收益低,撤销要 safepoint)。
  • 轻量级锁:第二个线程来竞争,栈里建 Lock Record,CAS 把 MarkWord 指向它;竞争线程自旋等待,不阻塞。
  • 重量级锁:自旋失败(竞争激烈/持锁久),膨胀为 ObjectMonitor,未抢到的线程 park 进 EntryList。

自适应自旋:JVM 根据该锁的历史自旋成功率动态调整自旋次数。

工程印证:锁升级解释了"低竞争下 synchronized 性能不差";jstack 看到线程 BLOCKED 在等 monitor 就是重量级;另外注意锁消除/锁粗化(JIT 优化),逃逸分析下局部 StringBuffer 的锁会被直接去掉。