JDK8中synchronized底层原理大白话版+学术版
最初没人抢 → 无锁。始终一个人进 → 偏向锁(记住你)。偶尔两个人抢 → 轻量级锁(原地等一会儿,不行就升级)。很多人抢 → 重量级锁(叫操作系统来排队)。这样设计是为了性能:绝大多数锁竞争并不激烈,用轻量级手段就够了。初始(无锁001)││ 第一个线程进入▼偏向锁(101)- thread ID││ 另一线程竞争▼轻量级锁(00) + 自旋││ 自旋超时 or 竞争加剧▼重量级锁(10) →
·
一、大白话版
1. synchronized 是干嘛的?
synchronized 是 Java 里解决“多线程抢同一把锁”问题的关键字。你可以把它想象成厕所的门锁:
- 一个线程进了厕所,就把门锁上(获得锁)。
- 其他线程想进,只能在门外等着(阻塞)。
- 里面的线程用完,打开门(释放锁),外面的人再抢。
2. JDK 8 里 synchronized 怎么实现“锁”?
JDK 8 不是一上来就用很重的操作系统锁,而是会根据竞争激烈程度自动升级锁,就像这样:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 无锁:初始状态,没任何人抢。
- 偏向锁:只有一个线程反复进厕所,门锁记住这个人的“名字”,以后他再来直接推门进,不检查。省事。
- 轻量级锁:来了另一个线程也想进,俩人轻度竞争。此时不惊动操作系统,用 CAS(自旋) 原地转圈等一会儿,如果很快能拿到锁,就省去操作系统阻塞的开销。
- 重量级锁:竞争激烈,自旋等太久了太耗CPU,于是交给操作系统管(mutex),线程被真正挂起,等着被唤醒。
3. 厕所门上的“小纸条”——对象头
每个 Java 对象头上有一小块内存,叫 Mark Word,就像门上贴的小纸条,记录锁的状态、线程ID、哈希码等。
4. 大白话总结
- 最初没人抢 → 无锁。
- 始终一个人进 → 偏向锁(记住你)。
- 偶尔两个人抢 → 轻量级锁(原地等一会儿,不行就升级)。
- 很多人抢 → 重量级锁(叫操作系统来排队)。
这样设计是为了性能:绝大多数锁竞争并不激烈,用轻量级手段就够了。
二、专业版(含对象头结构详解 & 锁升级原理)
1. 对象头结构(以 HotSpot JVM,64位为例)
Java 对象的内存布局分为三部分:
- 对象头 (Header)
- Mark Word(8字节)
- Klass Pointer(类型指针,默认开启指针压缩时为4字节)
- 实例数据 (Instance Data)
- 对齐填充 (Padding)
其中 Mark Word 是锁机制的核心。64位 JVM 下 Mark Word 各状态布局如下(图1):
无锁状态(001):
|--------------------------------------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | 001 |
|--------------------------------------------------|
63 0 (bit 位置简化)
偏向锁状态(101):
|-----------------------------------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | 偏向锁标记(1) | 锁标记(01) |
|-----------------------------------------------------------|
63 0
轻量级锁状态(00):
|--------------------------------------------------|
| 指向锁记录 (Lock Record) 的指针 | 00 |
|--------------------------------------------------|
63 0
重量级锁状态(10):
|--------------------------------------------------|
| 指向互斥量 (monitor) 的指针 | 10 |
|--------------------------------------------------|
63 0
GC标记(11):
|--------------------------------------------------|
| 空(或其他GC信息) | 11 |
|--------------------------------------------------|
注意:实际 bit 位分配因 JVM 版本和平台有细微差别,但核心逻辑不变。低位 2~3 位是锁标志位。
锁标志位对照表:
| 锁状态 | 标记位 (bits) |
|---|---|
| 无锁 | 001 |
| 偏向锁 | 101 |
| 轻量级 | 00 |
| 重量级 | 10 |
| GC标记 | 11 |
2. 锁升级详细流程
(1) 偏向锁 (Biased Locking)
- 目的:消除无竞争下的同步操作。
- 原理:当锁被第一个线程获取时,JVM 将 Mark Word 设为偏向模式,并记录线程 ID。以后该线程再进入同步块,只用检查 Mark Word 中的线程 ID 是否是自己(仅一次 CAS),不是则尝试重偏向或升级。
- JDK8 默认行为:偏向锁默认开启,但有 延迟(约 4 秒),防止 JVM 启动初期大量锁争用导致频繁偏向撤销。
- 撤销:当另一个线程尝试竞争偏向锁时,需要在全局安全点(Safepoint)撤销偏向锁,可能升级为轻量级锁。
(2) 轻量级锁 (Lightweight Locking)
- 触发:偏向锁失效或有另一个线程来竞争。
- 机制:
- 线程在自己的栈帧中创建 锁记录 (Lock Record),尝试用 CAS 将对象头的 Mark Word 替换为指向锁记录的指针。
- 若成功,线程获得轻量级锁,Mark Word 变为
00。 - 若 CAS 失败,说明有竞争。线程会 自旋(循环尝试几次,JDK 8 中自旋次数可通过
-XX:PreBlockSpin调整,自适应自旋也会开启)。 - 自旋一定次数后仍未成功,锁膨胀为重量级锁,Mark Word 变为
10,指针指向操作系统的 monitor 对象。
(3) 重量级锁 (Heavyweight Locking)
- 实现:基于操作系统的 mutex 互斥量。
- 行为:未获得锁的线程被阻塞(从用户态陷入内核态),进入等待队列。
- 开销:上下文切换成本高,但避免了 CPU 空转。
(4) 锁升级不可逆(除偏向锁可重偏向)
- 一旦升级为重量级锁,不会降级。这是设计决策,保证高竞争下的稳定。
3. 关键优化(JDK 8)
- 锁消除 (Lock Elimination):JIT 编译器分析出某个锁对象只被单线程访问时,直接去掉同步(依赖逃逸分析)。
- 锁粗化 (Lock Coarsening):JIT 将相邻的多个同步块合并为一个同步块,减少加锁/解锁次数。
- 自适应自旋 (Adaptive Spinning):自旋时间不再固定,根据前一次同一锁的自旋结果动态调整。
4. 源码层面的关键入口
ObjectMonitor::enter(重量级锁)synchronizer.cpp、biasedLocking.cpp- 字节码指令
monitorenter/monitorexit
5. 图文总结:锁升级状态迁移图
初始(无锁001)
│
│ 第一个线程进入
▼
偏向锁(101)- thread ID
│
│ 另一线程竞争
▼
轻量级锁(00) + 自旋
│
│ 自旋超时 or 竞争加剧
▼
重量级锁(10) → mutex 阻塞
6. 重要参数(JDK 8)
| JVM 参数 | 作用 |
|---|---|
-XX:+UseBiasedLocking |
开启偏向锁(默认 true) |
-XX:BiasedLockingStartupDelay |
偏向锁延迟时间(默认 4000 ms) |
-XX:+UseHeavyMonitors |
禁用锁升级(纯重量锁,调试用) |
-XX:PreBlockSpin |
轻量锁自旋次数(JDK 8 已自适应) |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)