synchronized 实现与三特性

java-concurrency 📚 learning synchronized-mechanism · synchronized · monitor · mark-word · lock-escalation · biased-lock · lightweight-lock · heavyweight-lock

TL;DR(30 秒扫完)

  • 对象头 = Mark Word(8 字节,存哈希码/GC 年龄/锁标志位/锁指针)+ Klass Pointer(4-8 字节)
  • 锁升级四条路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(只有升级没有降级)
  • JDK 15 默认关闭偏向锁,JDK 18 彻底移除(偏向锁撤销代价高、需 STW)
  • 三大特性底层:原子性=Monitor owner 互斥;可见性=解锁刷主存+加锁 reload;有序性=JIT 禁止指令重排
  • synchronized 是非公平锁(JVM 内部不保证先到先得)

关键结论

结论 Asynchronized 靠对象头 Mark Word 记录锁状态,锁升级从无锁→偏向锁→轻量级锁→重量级锁,只有升级没有降级
结论 B三大特性各自独立底层——原子性靠 Monitor owner 互斥,可见性靠解锁刷主存+加锁 reload,有序性靠 JIT 禁止指令重排
结论 Csynchronized 是非公平锁,JVM 内部不保证先到先得,需要公平性用 ReentrantLock(true)
结论 DJDK 15 后偏向锁被默认关闭,JDK 18 彻底移除——偏向锁撤销代价高、需 STW,现代 GC 下不划算

完整讲解(费曼四步)

STEP 1 · 概念

synchronized 是 Java 内置的同步关键字,同时保证原子性、可见性、有序性。它的底层是对象头(Mark Word)记录锁状态 + 锁升级机制 + Monitor(管程)。

STEP 2 · 大白话

类比:synchronized 就像一间只能进一个人的密室(Monitor)。门口有个门禁(CAS)——先尝试进,进不去就在门口排队(CLH 队列)。一旦进去,门就从里面锁上(owner 指针指向你),其他人只能等。
>
锁升级就像"逐步加严管理":刚开始只有一个常客(偏向锁,零开销)→ 来了第二个客人(轻量级锁,CAS 自旋)→ 来了很多客人排队(重量级锁,OS 阻塞)。

STEP 3 · 底层

对象头结构(HotSpot 64 位)

部分大小内容
Mark Word8 字节哈希码、GC 分代年龄、锁标志位(最后 2 位)、锁指针/偏向线程 ID
Klass Pointer4-8 字节指向方法区的类元数据
数组 length4-8 字节仅数组对象有
Mark Word 锁标志位(最后 2 位)决定当前锁状态:
  • 01 = 无锁
  • 00 + 锁标志位 01 = 偏向锁
  • 00 = 轻量级锁
  • 10 = 重量级锁
  • 11 = GC 标记(标记后回到无锁)

锁升级四条路径

阶段触发条件机制性能
无锁初始状态——
偏向锁只有一个线程访问Mark Word 记录线程 ID,同线程访问直接放行,不加锁不 CAS最轻量(零开销)
轻量级锁第二个线程到达撤销偏向锁,CAS 自旋尝试获取,不进入 OS 阻塞中等
重量级锁CAS 自旋失败达阈值(默认 10 次)膨胀为重量级锁,线程挂起到 Monitor entryList,OS 调度最重
关键规则:
  • 只有升级没有降级——JVM 保证锁状态单调递增
  • 偏向锁适合"一次获取、多次访问"的低竞争场景
  • 轻量级锁适合"锁持有时间极短"的中竞争场景
  • 重量级锁是最终兜底

JDK 演进

版本变化
JDK 6引入锁升级优化(偏向锁 + 轻量级锁)
JDK 15 (JEP 374)偏向锁默认关闭(-XX:-UseBiasedLocking)
JDK 18偏向锁彻底移除
废弃原因:偏向锁撤销代价高(需 STW),现代 GC(ZGC/Shenandoah)让偏向锁优化不划算;STW 时代结束偏向锁失去主要场景。

三大特性的底层机制

特性底层机制关键动作
原子性Monitor 互斥每个对象隐含 Monitor,含 owner 指针;CAS 尝试设置 owner;重量级锁下线程挂到 entryList
可见性解锁刷主存 + 加锁 reload解锁:工作内存强制刷回主存(StoreLoad 屏障);加锁:丢弃本地工作内存,强制从主存 reload
有序性JIT 禁止指令重排禁止把 synchronized 块内的指令重排到块外;CPU StoreLoad 屏障防止跨块重排
happens-before 第 17 条规则:对一个 synchronized 块的解锁,happens-before 后续对同一把锁的加锁。这是 JMM 保证可见性的契约基础。

三种使用场景

场景锁对象字节码
修饰实例方法this方法常量池 ACC_SYNCHRONIZED 标志
修饰静态方法Class 对象方法常量池 ACC_SYNCHRONIZED 标志
修饰代码块指定对象monitorenter / monitorexit / athrow

synchronized 锁对象深入(面试陷阱)

核心规则:锁的是"对象引用",不是"类型"或"代码块"
  • 非静态方法 → 锁 this(当前实例对象)
  • 静态方法 → 锁 Class.class(JVM 全局单例,所有实例共享一把锁)
  • synchronized(obj) → 锁 obj 引用指向的对象
Class 对象是全局单例:
  • 每个类加载后有一个唯一的 Class 实例
  • MyClass.class 就是这个引用
  • 因此 static synchronized 会让所有实例互相阻塞(即使两个不同实例)
工程实践建议:
  • 推荐私有 final 锁对象:private final Object lock = new Object();
  • 避免锁 this(外部代码能拿到)
  • 避免锁常量:synchronized("foo") 锁常量池 String,全 JVM 共享一把锁(大坑)

Monitor(管程)

重量级锁的真正实现。每个对象隐含一个 Monitor,包含:
  • owner:当前持锁线程
  • entryList:等待获取锁的线程队列
  • waitSet:wait() 挂起的线程集合

STEP 4 · 简化

一句话总结:synchronized 靠对象头 Mark Word 记录锁状态 + 锁升级四条路径(无锁→偏向锁→轻量级锁→重量级锁)+ 三大特性各自独立底层(Monitor 互斥 / 解锁刷主存 / JIT 禁重排)。是非公平锁,JDK 15 后偏向锁废弃。

常见误区

synchronized 是公平锁
是非公平锁,JVM 内部不保证先到先得
有序性 = 线程顺序获取锁
有序性指指令重排,不是线程调度顺序
锁可以降级
JVM 保证锁只有升级没有降级
锁住进程
Java 里是锁对象/代码块,不是锁进程

延伸追问

synchronized 的锁能降级吗?
不能。JVM 规定锁只有升级没有降级,避免锁状态频繁切换的开销
偏向锁为什么被废弃了?
JDK 15 JEP 374 默认关闭,JDK 18 移除。偏向锁撤销代价高(需 STW),现代 GC 让偏向锁优化不划算
synchronized 和 ReentrantLock 怎么选?
低竞争用 synchronized(JVM 内置、零开销、代码简洁);高竞争、需要中断/超时/公平锁/多条件变量用 ReentrantLock

速查表

对象头结构:
  Mark Word(8 字节)= 哈希码 + GC 年龄 + 锁标志位 + 锁指针
  Klass Pointer(4-8 字节)

锁升级路径:
  无锁 → 偏向锁(零开销)→ 轻量级锁(CAS 自旋)→ 重量级锁(OS 阻塞)
  只有升级没有降级
  JDK 15 关闭偏向锁,JDK 18 移除

三特性底层:
  原子性 = Monitor owner 互斥
  可见性 = 解锁刷主存 + 加锁 reload
  有序性 = JIT 禁止指令重排 + StoreLoad 屏障

关键术语:
  synchronized 是非公平锁
  锁对象/代码块(非"锁住进程")
  有序性=指令重排(非"线程顺序获取锁")

Anki 候选卡片

Q: synchronized 的对象头结构?
A: Mark Word(8 字节:哈希码+GC 年龄+锁标志位+锁指针)+ Klass Pointer(4-8 字节)
Q: synchronized 锁升级的四条路径?
A: 无锁→偏向锁(零开销)→轻量级锁(CAS 自旋)→重量级锁(OS 阻塞),只有升级没有降级
Q: synchronized 三大特性各自底层机制?
A: 原子性=Monitor owner 互斥;可见性=解锁刷主存+加锁 reload;有序性=JIT 禁止指令重排
Q: synchronized 是公平锁还是非公平锁?
A: 非公平锁,JVM 内部不保证先到先得
Q: 偏向锁为什么被废弃?
A: 撤销代价高需 STW,现代 GC 下不划算;JDK 15 默认关闭,JDK 18 彻底移除
Q: happens-before 第 17 条规则?
A: synchronized 块的解锁 happens-before 后续对同一把锁的加锁

关联题目

  • [ ] 《synchronized是怎么实现的?》— 2026-09-26 Round 1 Q6, ⭐⭐⭐
  • [ ] 《synchronized是如何保证原子性、可见性、有序性的?》— 2026-09-26 Round 1 Q8, ⭐⭐

关联知识

对象头 Mark Word 记录锁状态;锁升级四条路径无锁→偏向锁→轻量级→重量级;三大特性各自独立底层;非公平锁;JDK 15 偏向锁默认关、JDK 18 彻底移除
密室类比:synchronized 像只能进一个人的密室(Monitor),门口有门禁(CAS),进不去就排队(CLH 队列),进去后门从里面锁上(owner 指向你)
✦ 记 忆 口 诀 ✦
Mark Word 记录锁状态;四条路径无降级;三大特性=Monitor 互斥/刷主存+reload/JIT 禁重排;非公平锁;JDK 15/18 偏向锁废弃
关键可视化
锁升级四条路径
flowchart LR
  N["无锁<br/>初始状态"] --> B["偏向锁<br/>单线程访问<br/>Mark Word 记线程 ID<br/>零开销"]
  B --> L["轻量级锁<br/>第二个线程到达<br/>CAS 自旋<br/>不 OS 阻塞"]
  L --> H["重量级锁<br/>CAS 失败达阈值<br/>OS 阻塞<br/>Monitor 挂起"]
  style N fill:#e0e0e0
  note["只有升级没有降级<br/>JDK 15 偏向锁默认关<br/>JDK 18 偏向锁移除"]
对象头结构(HotSpot 64 位)
flowchart TB
  H["对象头"] --> MW["Mark Word<br/>8 字节"]
  H --> KP["Klass Pointer<br/>4-8 字节<br/>指向方法区类元数据"]
  MW --> F1["哈希码"]
  MW --> F2["GC 分代年龄"]
  MW --> F3["锁标志位(最后 2 位)"]
  MW --> F4["锁指针/偏向线程 ID"]
Mark Word 锁标志位
flowchart LR
  F["最后 2 位"] --> F1["01 = 无锁"]
  F --> F2["00 + 锁标志位 01 = 偏向锁"]
  F --> F3["00 = 轻量级锁"]
  F --> F4["10 = 重量级锁"]
  F --> F5["11 = GC 标记"]
三大特性底层机制
flowchart TB
  S["synchronized"] --> A["原子性 ✓<br/>Monitor owner 互斥<br/>CAS 尝试设置 owner"]
  S --> V["可见性 ✓<br/>解锁刷主存<br/>加锁 reload"]
  S --> O["有序性 ✓<br/>JIT 禁止指令重排<br/>StoreLoad 屏障"]
  O -.契约.-> HB["happens-before 第 17 条<br/>解锁 hb 后续加锁"]
synchronized 锁对象三种
flowchart LR
  S["synchronized"] --> C1["非静态方法<br/>锁 this"]
  S --> C2["静态方法<br/>锁 Class\<T\>.class<br/>JVM 全局单例"]
  S --> C3["代码块<br/>锁显式对象<br/>推荐 private final Object lock"]
  C3 -.陷阱.-> X["synchronized(\"foo\")<br/>锁常量池 String<br/>全 JVM 共享一把锁"]
知识关系

⬆️ 前置(Prerequisite)

concurrency-foundations

🔄 延伸(Extends)

jmm-volatile-casaqs-mechanism

⚡ 对比(Contrast)

ReentrantLock— synchronized 是 JVM 内置、不可中断、不可超时;ReentrantLock 用户态、可中断、可超时、可选公平
🎯 概念 📏 规则 ⚠️ 误区 🔍 追问 ✨ 口诀 共 0 张卡,点击翻面