第52篇:synchronized深度解析(2026版)
📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第51篇:线程生命周期与创建方式 |
➡️ 下一篇:第53篇:volatile与JMM(待发布)
🗺️ 本文阅读地图(3 分钟速览)
第51篇学会了创建线程,本篇深入线程安全的核心——synchronized。从对象头到锁升级,从字节码到JVM优化,彻底搞懂这把“万能锁”:
📌系列导航: 《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
- 八、生产级避坑清单
- 九、面试高频考点
- 🎤 面试官追问陷阱(加分题)
- 十、练习题
- 📊 你的学习进度
- 👉 下一篇文章预告
- 第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 实现,保证多线程环境下共享资源的原子性、可见性与有序性,同时具备自动释放锁、可重入 的特性。 三种使用方式 : 锁升级路径(JDK 1.6+) : JDK 1.6 之前 synchronized 只有重量级锁,性能差。JDK 1.6 引入锁升级机制,根据竞争程度动态调整锁形态。JDK 15+ 默认禁用偏向锁( ),但仍需理解其原理。 JVM 优化手段 : synchronized 就像一个公共厕所 : 锁升级就像厕所管理的优化 : synchronized 的锁状态完全存储在 Java 对象头的Mark Word 中。 在 64 位 JVM 中,Java 对象的内存布局分为三部分: Mark Word 是锁的核心载体 : 💡关键判断 :仅靠锁标志位无法区分无锁和偏向锁(都是 01),需配合偏向标志位(biased_lock)一起判断。 触发条件 :对象刚创建,尚未被任何线程锁定。 获取流程 : 偏向锁的核心思想 :如果一个线程反复获取同一把锁,每次都要 CAS 操作也是有开销的。偏向锁让该线程“记住”自己,下次直接进入,省去 CAS。 释放 :偏向锁不会主动释放。当其他线程尝试获取时,触发偏向撤销。 触发条件 :另一个线程尝试获取已被偏向的锁。 升级流程 : 轻量级锁的实现 : 触发条件 :自旋次数超过阈值(JVM 自适应),或另一个线程也来竞争。 升级流程 : ObjectMonitor 核心结构 : synchronized 是可重入锁 ——同一个线程可以多次获取同一把锁。 实现原理 : Q1:synchronized 的三种使用方式?锁对象分别是什么? ① 修饰实例方法 → 锁当前实例 对象;③ 修饰代码块 → 锁括号内指定的对象。 Q2:synchronized 的锁存在哪里? 存在 Java 对象头的Mark Word 中。64 位 JVM 中 Mark Word 占 8 字节,存储锁状态、线程 ID、hashCode 等信息。 Q3:synchronized 锁升级的过程? 从低到高依次为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁 ,单向升级不可逆。JDK 1.6 引入,JDK 15+ 默认禁用偏向锁。 Q4:偏向锁、轻量级锁、重量级锁分别解决什么问题? 偏向锁针对无竞争场景,避免 CAS 开销;轻量级锁针对轻度竞争,通过自旋避免线程阻塞;重量级锁针对重度竞争 ,通过 OS 互斥量阻塞线程。 Q5:synchronized 的可重入性是如何实现的? 每个对象关联的 ,归零时释放锁。 Q6:JVM 对 synchronized 做了哪些优化? ①锁升级(偏向→轻量→重量);②自适应自旋锁(动态调整自旋次数);③锁消除(逃逸分析移除无竞争锁);④锁粗化 (合并连续加锁/解锁)。 追问1 :“JDK 15 为什么默认禁用偏向锁?” 👉 偏向锁的维护成本(撤销、安全点)在大多数应用场景下超过了其带来的收益。现代应用通常有多个线程竞争,偏向锁优势不明显。可通过 重新启用。 追问2 :“轻量级锁一定比重量级锁快吗?” 👉不一定 。轻量级锁通过自旋等待,适用于锁持有时间短、竞争不激烈的场景。如果锁持有时间长或竞争激烈,自旋会浪费 CPU,此时重量级锁让线程阻塞反而更高效。JVM 的自适应自旋会根据历史统计动态决策。 追问3 :“虚拟线程中为什么慎用 synchronized?” 👉 虚拟线程在 替代。 分析题:以下代码中,两个线程分别调用 会互相阻塞吗?为什么? 💡 思路: ,两者互不影响。 代码题 :用 JOL 工具打印一个普通对象的对象头布局,观察无锁状态下的 Mark Word。 场景设计 :某系统使用 synchronized 保护共享资源,并发量高时性能急剧下降。分析可能的原因和优化方案。 💡 思路:可能是锁粒度太大(锁整个方法)或锁持有时间过长。优化方案:缩小锁粒度(锁代码块)、减少锁内操作、考虑使用 ReentrantLock 或 ConcurrentHashMap。 👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇 🚀下一篇:《第53篇:volatile与JMM》 内容简介 :Java 内存模型(JMM)、可见性、有序性、禁止指令重排、happens-before 原则、volatile 与 synchronized 的区别。 👉多线程专题持续深入,拿下 JMM 核心! 📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer! 👉 点击关注我,更新后第一时间收到推送!使用方式 锁对象 作用范围 修饰实例方法 当前实例 this同一实例的所有同步方法互斥 修饰静态方法 当前类的 Class对象该类所有实例的静态同步方法互斥 修饰代码块 括号内指定的对象 锁粒度最细,灵活性最高 无锁 → 偏向锁 → 轻量级锁 → 重量级锁(单向升级,不可逆)-XX:-UseBiasedLocking二、生活类比:从“公共厕所”到“VIP 厕所”
三、Java 对象布局与 Mark Word
锁状态 锁标志位(2bit) 偏向标志(1bit) Mark Word 存储内容 无锁(不可偏向) 01 0 对象 hashCode + 分代年龄 无锁(可偏向) 01 1 空,可存储偏向线程 ID 偏向锁 01 1 偏向线程 ID(54bit)+ epoch(2bit)+ 分代年龄(4bit) 轻量级锁 00 — 指向线程栈中 Lock Record 的指针(62bit) 重量级锁 10 — 指向 ObjectMonitor 对象的指针(62bit) GC 标记 11 — 空 四、锁升级全流程
4.1 无锁 → 偏向锁
4.3 轻量级锁 → 重量级锁_EntryList阻塞等待
4.4 锁升级流程总结ObjectMonitor { _owner // 持有锁的线程 _count // 锁计数器(可重入) _recursions // 重入次数 _EntryList // 等待获取锁的线程队列 _WaitSet // 调用 wait() 的线程队列 }五、可重入性原理
ObjectMonitor,内部维护_count计数器_owner = 当前线程,_count = 1_count++(不阻塞)_count--_count == 0时真正释放锁publicsynchronizedvoidmethodA(){methodB();// 同一把锁,可重入!}publicsynchronizedvoidmethodB(){// 直接进入,不需要重新竞争}六、JVM 对 synchronized 的优化
6.1 自适应自旋锁
6.2 锁消除(Lock Elimination)
6.3 锁粗化(Lock Coarsening)StringBuffer的方法都是 synchronized,但在局部变量中只被单线程访问时,JVM 会消除锁七、synchronized vs ReentrantLock
对比维度 synchronized ReentrantLock 实现层级 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九、面试高频考点
this;② 修饰静态方法 → 锁当前类的ClassObjectMonitor维护_count计数器。同一线程重复获取时_count++,退出时_count--🎤 面试官追问陷阱(加分题)
-XX:+UseBiasedLockingsynchronized块中会被固定在载体线程上(Pinning),无法被卸载,失去轻量级优势。高并发场景建议用ReentrantLock十、练习题
methodA()和methodB()publicclassTest{publicsynchronizedvoidmethodA(){...}publicstaticsynchronizedvoidmethodB(){...}}methodA()锁的是实例this,methodB()锁的是Test.class📊 你的学习进度
👉 下一篇文章预告