《Java 100 天进阶之路》第52篇:synchronized深度解析(2026版)

第52篇:synchronized深度解析(2026版)

📌系列导航:

《Java 100 天进阶之路》完整目录 |

⬅️ 上一篇:

第51篇:线程生命周期与创建方式 |

➡️ 下一篇:

第53篇:volatile与JMM(待发布)

🗺️ 本文阅读地图(3 分钟速览)

第51篇学会了创建线程,本篇深入线程安全的核心——synchronized

。从对象头到锁升级,从字节码到JVM优化,彻底搞懂这把“万能锁”:

模块核心问题一句话回答
三种用法synchronized 怎么用?修饰实例方法(锁this)、静态方法(锁Class)、代码块(自定义锁对象)
底层原理锁存在哪里?Java对象头的Mark Word中,64位JVM占8字节
锁升级性能怎么优化?无锁 → 偏向锁 → 轻量级锁 → 重量级锁,逐级升级,不可逆
JVM优化还有哪些优化手段?自适应自旋锁、锁消除、锁粗化
面试最爱问高频考点有哪些?见文末 🎤 小节

文章目录

    • 第52篇:synchronized深度解析(2026版)
      • 🗺️ 本文阅读地图(3 分钟速览)
      • 一、核心知识点
      • 二、生活类比:从“公共厕所”到“VIP 厕所”
      • 三、Java 对象布局与 Mark Word
      • 四、锁升级全流程
        • 4.1 无锁 → 偏向锁
        • 4.2 偏向锁 → 轻量级锁
        • 4.3 轻量级锁 → 重量级锁
        • 4.4 锁升级流程总结
      • 五、可重入性原理
      • 六、JVM 对 synchronized 的优化
        • 6.1 自适应自旋锁
        • 6.2 锁消除(Lock Elimination)
        • 6.3 锁粗化(Lock Coarsening)
      • 七、synchronized vs ReentrantLock
      • 八、生产级避坑清单
      • 九、面试高频考点
      • 🎤 面试官追问陷阱(加分题)
      • 十、练习题
    • 📊 你的学习进度
    • 👉 下一篇文章预告

一、核心知识点

synchronized 是什么?

synchronized 是 Java 内置的互斥同步锁,基于 JVM 实现,保证多线程环境下共享资源的原子性、可见性与有序性,同时具备自动释放锁、可重入

的特性。

三种使用方式

:

使用方式锁对象作用范围
修饰实例方法当前实例this同一实例的所有同步方法互斥
修饰静态方法当前类的Class对象该类所有实例的静态同步方法互斥
修饰代码块括号内指定的对象锁粒度最细,灵活性最高

锁升级路径(JDK 1.6+)

:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁(单向升级,不可逆)

JDK 1.6 之前 synchronized 只有重量级锁,性能差。JDK 1.6 引入锁升级机制,根据竞争程度动态调整锁形态。JDK 15+ 默认禁用偏向锁(-XX:-UseBiasedLocking

),但仍需理解其原理。

JVM 优化手段

:

  • 自适应自旋锁:JVM 自动统计锁竞争情况,决定自旋等待还是阻塞
  • 锁消除:JIT 通过逃逸分析,检测到不可能存在竞争时直接移除锁
  • 锁粗化:将多个连续的加锁/解锁操作合并为一次大范围加锁

二、生活类比:从“公共厕所”到“VIP 厕所”

synchronized 就像一个公共厕所

:

  • 厕所只有一间(锁对象),门口挂着一个“占用/空闲”指示牌(Mark Word)。
  • 有人进去了,牌子翻到“占用”(获取锁),其他人只能在门口排队(BLOCKED)。
  • 里面的人出来,牌子翻回“空闲”(释放锁),排队的人抢着进(锁竞争)。

锁升级就像厕所管理的优化

:

  • 无锁:厕所空着,谁都能进,但没人进。
  • 偏向锁(VIP 厕所):社区给固定住户配了一把专属钥匙(偏向线程 ID),每天都是同一个人用,省去了每次翻牌子的麻烦。
  • 轻量级锁(自旋等待):偶尔有客人来,客人不直接排队,而是在门口原地转圈等待(自旋),期望里面的人很快出来。
  • 重量级锁(正式排队):客人越来越多,原地转圈太耗体力,社区正式安排保安维持秩序,排队的人进入等待队列(OS 线程阻塞)。


三、Java 对象布局与 Mark Word

synchronized 的锁状态完全存储在 Java 对象头的Mark Word

中。

在 64 位 JVM 中,Java 对象的内存布局分为三部分:

  1. 对象头(Object Header):Mark Word(8 字节)+ Klass Pointer(4 字节,压缩后)
  2. 实例数据(Instance Data):成员变量
  3. 对齐填充(Padding):保证对象大小是 8 的倍数

Mark Word 是锁的核心载体

:

锁状态锁标志位(2bit)偏向标志(1bit)Mark Word 存储内容
无锁(不可偏向)010对象 hashCode + 分代年龄
无锁(可偏向)011空,可存储偏向线程 ID
偏向锁011偏向线程 ID(54bit)+ epoch(2bit)+ 分代年龄(4bit)
轻量级锁00—指向线程栈中 Lock Record 的指针(62bit)
重量级锁10—指向 ObjectMonitor 对象的指针(62bit)
GC 标记11—空

💡关键判断

:仅靠锁标志位无法区分无锁和偏向锁(都是 01),需配合偏向标志位(biased_lock)一起判断。


四、锁升级全流程

4.1 无锁 → 偏向锁

触发条件

:对象刚创建,尚未被任何线程锁定。

获取流程

:

  1. 线程尝试获取锁,检查 Mark Word 是否处于可偏向状态(标志位 01 + 偏向标志 0)
  2. 通过 CAS 将当前线程 ID 写入 Mark Word
  3. CAS 成功 → 进入偏向锁状态,Mark Word 存储该线程 ID
  4. CAS 失败 → 说明有其他线程竞争,撤销偏向锁,升级为轻量级锁

偏向锁的核心思想

:如果一个线程反复获取同一把锁,每次都要 CAS 操作也是有开销的。偏向锁让该线程“记住”自己,下次直接进入,省去 CAS。

释放

:偏向锁不会主动释放。当其他线程尝试获取时,触发偏向撤销。

4.2 偏向锁 → 轻量级锁

触发条件

:另一个线程尝试获取已被偏向的锁。

升级流程

:

  1. 线程 B 尝试获取锁,发现 Mark Word 中的线程 ID 不是自己
  2. 触发偏向撤销,JVM 暂停持有锁的线程 A(到达安全点)
  3. 检查线程 A 是否存活、是否还在同步块中
  4. 若线程 A 已退出同步块,撤销偏向,恢复为无锁状态,再 CAS 竞争
  5. 若线程 A 仍在同步块中,升级为轻量级锁

轻量级锁的实现

:

  • 线程 B 在自己的栈帧中创建一个Lock Record(锁记录)
  • 通过 CAS 将 Mark Word 替换为指向 Lock Record 的指针
  • CAS 成功 → 线程 B 获取轻量级锁
  • CAS 失败 → 说明有竞争,自旋等待
4.3 轻量级锁 → 重量级锁

触发条件

:自旋次数超过阈值(JVM 自适应),或另一个线程也来竞争。

升级流程

:

  1. 轻量级锁 CAS 替换 Mark Word 失败,说明存在锁竞争
  2. JVM 将锁膨胀为重量级锁
  3. Mark Word 替换为指向ObjectMonitor的指针
  4. 竞争失败的线程进入_EntryList阻塞等待

ObjectMonitor 核心结构

:

ObjectMonitor { _owner // 持有锁的线程 _count // 锁计数器(可重入) _recursions // 重入次数 _EntryList // 等待获取锁的线程队列 _WaitSet // 调用 wait() 的线程队列 }
4.4 锁升级流程总结


五、可重入性原理

synchronized 是可重入锁

——同一个线程可以多次获取同一把锁。

实现原理

:

  1. 每个对象关联一个ObjectMonitor,内部维护_count计数器
  2. 线程第一次获取锁:_owner = 当前线程,_count = 1
  3. 同一线程再次进入:_count++(不阻塞)
  4. 退出同步块:_count--
  5. _count == 0时真正释放锁
publicsynchronizedvoidmethodA(){methodB();// 同一把锁,可重入!}publicsynchronizedvoidmethodB(){// 直接进入,不需要重新竞争}

六、JVM 对 synchronized 的优化

6.1 自适应自旋锁
  • 线程尝试获取轻量级锁失败时,不立即阻塞,而是原地循环等待(自旋)
  • JVM 会统计每个锁的自旋成功率,动态调整自旋次数
  • 成功率高 → 多自旋一会儿;成功率低 → 少自旋或直接阻塞
6.2 锁消除(Lock Elimination)
  • JIT 通过逃逸分析判断锁对象是否只被单个线程访问
  • 如果不存在竞争,JVM 直接移除 synchronized
  • 示例:StringBuffer的方法都是 synchronized,但在局部变量中只被单线程访问时,JVM 会消除锁
6.3 锁粗化(Lock Coarsening)
  • 将多个连续的加锁/解锁操作合并成一次大范围加锁
  • 减少锁获取/释放的次数,降低开销
  • 示例:循环内反复加锁 → JVM 把锁移到循环外

七、synchronized vs ReentrantLock

对比维度synchronizedReentrantLock
实现层级JVM 内置关键字JDK 提供的 API 类
锁释放自动释放(异常也释放)需在 finally 中手动 unlock()
可重入性✅ 支持✅ 支持
公平锁❌ 非公平支持公平/非公平
中断响应❌ 不可中断✅lockInterruptibly()
超时获取❌ 不支持✅tryLock(timeout)
条件变量wait()/notify()Condition(多个)
性能低竞争时接近,高竞争时略逊高竞争时略优
锁升级✅ 有(偏向→轻量→重量)❌ 无
适用场景简单同步,低/中竞争高级并发控制,高竞争

八、生产级避坑清单

✅ synchronized 使用避坑指南 1. 锁对象不能用 null → 抛 NullPointerException 2. 锁对象不能用基本类型 → 只能用引用类型 3. 不要用 String 常量作为锁对象 → 不同模块可能共用同一字符串常量池,导致意外死锁 4. 不要在锁内执行耗时操作 → 长时间持有锁,阻塞其他线程 5. 注意锁粒度 → 能锁代码块就别锁整个方法 6. wait() 必须在 synchronized 块中调用 → 否则抛 IllegalMonitorStateException 7. 虚拟线程中慎用 synchronized → 会导致 Pinning(固定在载体线程),建议用 ReentrantLock

九、面试高频考点

Q1:synchronized 的三种使用方式?锁对象分别是什么?

① 修饰实例方法 → 锁当前实例this;② 修饰静态方法 → 锁当前类的Class

对象;③ 修饰代码块 → 锁括号内指定的对象。

Q2:synchronized 的锁存在哪里?

存在 Java 对象头的Mark Word

中。64 位 JVM 中 Mark Word 占 8 字节,存储锁状态、线程 ID、hashCode 等信息。

Q3:synchronized 锁升级的过程?

从低到高依次为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

,单向升级不可逆。JDK 1.6 引入,JDK 15+ 默认禁用偏向锁。

Q4:偏向锁、轻量级锁、重量级锁分别解决什么问题?

偏向锁针对无竞争场景,避免 CAS 开销;轻量级锁针对轻度竞争,通过自旋避免线程阻塞;重量级锁针对重度竞争

,通过 OS 互斥量阻塞线程。

Q5:synchronized 的可重入性是如何实现的?

每个对象关联的ObjectMonitor维护_count计数器。同一线程重复获取时_count++,退出时_count--

,归零时释放锁。

Q6:JVM 对 synchronized 做了哪些优化?

①锁升级(偏向→轻量→重量);②自适应自旋锁(动态调整自旋次数);③锁消除(逃逸分析移除无竞争锁);④锁粗化

(合并连续加锁/解锁)。


🎤 面试官追问陷阱(加分题)

追问1

:“JDK 15 为什么默认禁用偏向锁?”

👉 偏向锁的维护成本(撤销、安全点)在大多数应用场景下超过了其带来的收益。现代应用通常有多个线程竞争,偏向锁优势不明显。可通过-XX:+UseBiasedLocking

重新启用。

追问2

:“轻量级锁一定比重量级锁快吗?”

👉不一定

。轻量级锁通过自旋等待,适用于锁持有时间短、竞争不激烈的场景。如果锁持有时间长或竞争激烈,自旋会浪费 CPU,此时重量级锁让线程阻塞反而更高效。JVM 的自适应自旋会根据历史统计动态决策。

追问3

:“虚拟线程中为什么慎用 synchronized?”

👉 虚拟线程在synchronized块中会被固定在载体线程上(Pinning),无法被卸载,失去轻量级优势。高并发场景建议用ReentrantLock

替代。


十、练习题

  1. 分析题:以下代码中,两个线程分别调用methodA()和methodB()

    会互相阻塞吗?为什么?

    publicclassTest{publicsynchronizedvoidmethodA(){...}publicstaticsynchronizedvoidmethodB(){...}}

    💡 思路:methodA()锁的是实例this,methodB()锁的是Test.class

    ,两者互不影响。

  2. 代码题

    :用 JOL 工具打印一个普通对象的对象头布局,观察无锁状态下的 Mark Word。

  3. 场景设计

    :某系统使用 synchronized 保护共享资源,并发量高时性能急剧下降。分析可能的原因和优化方案。

    💡 思路:可能是锁粒度太大(锁整个方法)或锁持有时间过长。优化方案:缩小锁粒度(锁代码块)、减少锁内操作、考虑使用 ReentrantLock 或 ConcurrentHashMap。


📊 你的学习进度

  • 当前:第52篇 / 共108篇 ·进阶篇:并发编程与JUC详解(第51~60篇)
  • ✅ 已完成:基础篇44篇 + 第45~52篇
  • 📖 正在学:第52篇
  • ⏳ 待学习:第53~108篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

👉 下一篇文章预告

🚀下一篇:《第53篇:volatile与JMM》

内容简介

:Java 内存模型(JMM)、可见性、有序性、禁止指令重排、happens-before 原则、volatile 与 synchronized 的区别。

👉多线程专题持续深入,拿下 JMM 核心!

📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer! 👉 点击关注我,更新后第一时间收到推送!