Skip to content
极高困难

一句话答案

synchronized 底层靠 Monitor 实现,JDK6 引入锁升级:无锁→偏向锁(单线程)→轻量级锁(CAS)→重量级锁(阻塞)。

核心要点

synchronized 的底层实现(Monitor 对象监视器):

每个 Java 对象都有一个关联的 Monitor(监视器),存储在对象头(Mark Word)中。

代码块锁:
synchronized(obj) { ... }
↓ 编译为字节码:
monitorenter   ← 获取 obj 关联的 Monitor,计数+1
  ...
monitorexit    ← 释放 Monitor,计数-1,计数=0时唤醒等待线程

方法锁:
synchronized void method() { ... }
↓ 方法访问标志中设置 ACC_SYNCHRONIZED,JVM 执行时自动 monitorenter/monitorexit

锁升级(JDK 6 引入,解决 synchronized 重量级锁性能问题):

锁状态存储在对象头的 Mark Word 中,随竞争程度单向升级(不可降级,JDK 15 后偏向锁被移除):

无锁 → 偏向锁 → 轻量级锁(自旋锁)→ 重量级锁

无锁
  ↓ 第一个线程访问
偏向锁(Biased Locking)
  - Mark Word 记录持有该锁的线程 ID
  - 同一线程再次获取:直接检查 Mark Word 中的线程 ID,无需 CAS,极快
  - 有第二个线程竞争时升级 ↓

轻量级锁(Thin Lock)
  - 线程在自己的栈帧中创建 Lock Record(锁记录)
  - 通过 CAS 将 Lock Record 地址写入 Mark Word
  - CAS 成功 = 获锁;CAS 失败 = 有竞争 → 自旋等待
  - 自旋超过阈值(默认10次,JVM 自适应)还未获锁 → 升级 ↓

重量级锁(Fat Lock)
  - Mark Word 指向 ObjectMonitor 对象(C++ 实现)
  - 获锁失败的线程进入 EntryList(阻塞等待)
  - 持锁线程 wait() 后进入 WaitSet(等待被 notify 唤醒)
  - OS 级别的互斥量(Mutex),涉及用户态/内核态切换,开销大

为什么自旋后才升级重量级锁: 如果持锁时间很短(如纳秒级),与其让线程阻塞(涉及上下文切换,微秒级开销),不如让它 CAS 自旋几次等待,避免不必要的内核调用。

追问与易错

追问方向:

  • 锁升级后能降级吗?(一般不可逆,特殊情况 GC 时可能降级)
  • JDK15 为什么默认关闭偏向锁?(偏向撤销代价大,现代应用竞争多)
  • synchronized 加在方法和代码块上的区别?(方法用 ACC_SYNCHRONIZED 标志,代码块用 monitorenter/exit)

易错点:

  • ❌ "synchronized 性能很差"——JDK6 优化后轻量级锁开销很小
  • ❌ 不理解"锁的是对象不是代码"——不同对象实例互不影响

💡 记忆锚点

对象头里藏着一把锁:没人竞争时偏向一个人(偏向锁,记线程 ID 免检通行),有人来抢就 CAS 自旋等(轻量级锁),等太久就交给 OS 排队(重量级锁)——从自助到排队窗口的升级过程,只升不降。