《锁侠闯江湖:小白通关Java synchronized底层秘境》
想象一下,你写的Java代码就是一个繁华的"对象城"。城里有很多"线程大侠"(Thread),他们武艺高强,飞檐走壁。但问题来了,城里有一个藏宝阁(共享资源),里面放着绝世珍宝——一个叫做 count 的变量。
如果所有大侠一拥而入,你抢我夺,count 的值就会错乱,江湖大乱。
这时,就需要一位 "锁侠"(synchronized)来守护藏宝阁,维持秩序。
第一幕:初入江湖——锁的三种招式
锁侠有三种不同的形态,对应不同的江湖场景:
招式一:招式锁(同步方法)
public synchronized void addCount() {count++;
}
这就像给整个藏宝阁大门加锁,一次只允许一位大侠进入。
招式二:兵器锁(同步代码块)
public void addCount() {synchronized(this) { // "this"就是这把锁的兵器count++;}
}
更灵活,只锁藏宝阁里最重要的那个宝箱,而不是整个大厅。
招式三:门派锁(同步类对象)
public static synchronized void staticMethod() {// ...
}
这锁的是整个"门派"(Class对象),所有这个门派的弟子(实例)都受此锁约束。
小白问:这些招式有什么区别?
招式锁:锁的是当前实例对象(
this)门派锁:锁的是类的Class对象
兵器锁:最灵活,可以指定任意对象作为"锁"
第二幕:锁的进化论——从少年到宗师
这才是最精彩的部分!锁侠不是生来就那么强大的,他经历了四次进化:
阶段一:无锁状态(Lockless)
-
刚出道的锁侠,藏宝阁谁都能进,一片混乱
-
适用于:线程安全的对象(如只读对象)
阶段二:偏向锁(Biased Locking) - "我的地盘我做主"
-
故事:第一个来到藏宝阁的大侠A,锁侠说:"好吧,我看你面善,以后这锁就偏向你了!"
-
原理:在对象头的Mark Word中记录线程ID,以后这个大侠A再来,直接放行,无需任何检查
-
目的:在几乎没有竞争的场景下,消除同步开销
-
触发条件:只有一个线程访问同步块
阶段三:轻量级锁(Lightweight Locking) - "文斗不武斗"
-
故事:大侠B也来了!"等等,这里已经有人了!"但两位大侠都很文明,决定通过 CAS(Compare And Swap) 这种"文斗"方式竞争
-
原理:在各线程的栈帧中创建锁记录(Lock Record),通过CAS操作尝试将对象头指向自己的锁记录
-
特点:线程不会阻塞,通过自旋等待
-
触发条件:多个线程交替执行,没有真正的同时竞争
阶段四:重量级锁(Heavyweight Locking) - "惊动朝廷"
-
故事:大侠C、D、E...都来了!文斗解决不了,只能请出朝廷的 "Monitor(管程)" 来维持秩序
-
原理:线程进入阻塞状态,放入等待队列,等待操作系统调度
-
特点:真正的互斥,开销最大
-
触发条件:多线程激烈竞争
第三幕:锁的内心世界——对象头探秘(详细注释版)
每个Java对象(藏宝阁)都有个 对象头(Object Header),就像身份证一样记录着锁的状态。在64位JVM中,对象头长这样:

Mark Word就是锁状态的"身份证",不同锁状态下它的结构完全不同:
1. 无锁状态(刚创建的对象)

详细解释:
-
identity_hashcode(31位):对象的哈希码,用于HashMap等
-
age(4位):对象的分代年龄(用于垃圾回收,最大15)
-
biased_lock(1位):偏向锁标志,0表示未启用偏向锁
-
lock(2位):锁标志位,01表示无锁状态
2. 偏向锁(第一个线程访问时)

详细解释:
-
thread(54位):持有偏向锁的线程ID
-
epoch(2位):偏向时间戳,用于批量撤销偏向锁
-
biased_lock(1位):偏向锁标志,1表示启用偏向锁
-
lock(2位):锁标志位,01与无锁状态相同,靠biased_lock区分
3. 轻量级锁(有竞争但不太激烈)

详细解释:
-
ptr_to_lock_record(62位):指向线程栈帧中锁记录的指针
-
lock(2位):锁标志位,00表示轻量级锁
4. 重量级锁(竞争激烈时)

详细解释:
-
ptr_to_heavyweight_monitor(62位):指向重量级锁Monitor对象的指针
-
lock(2位):锁标志位,10表示重量级锁
锁标志位总结表
| 锁状态 | biased_lock | lock | 说明 |
|---|---|---|---|
| 无锁 | 0 | 01 | 刚创建的对象 |
| 偏向锁 | 1 | 01 | 第一个线程访问 |
| 轻量级锁 | - | 00 | 有竞争但不太激烈 |
| 重量级锁 | - | 10 | 竞争激烈 |
| GC标记 | - | 11 | 对象被GC标记 |
关键洞察:看到这个表格了吗?JVM就是通过检查
biased_lock和lock这两个标志位来判断当前对象的锁状态的!这就是锁升级的"密码本"。
第四幕:终极武器——Monitor管程
重量级锁的核心就是Monitor,可以把它想象成一个智能门禁系统:
ObjectMonitor() {_header = NULL;_count = 0; // 重入次数_waiters = 0, // 等待线程数_recursions = 0; // 锁的重入次数_object = NULL;_owner = NULL; // 当前持有锁的线程_WaitSet = NULL; // 处于wait状态的线程队列_WaitSetLock = 0 ;_EntryList = NULL ; // 处于等待锁block状态的线程队列
}
工作流程:
-
线程尝试通过CAS获取锁,成功则成为
_owner -
失败则进入
_EntryList等待 -
获得锁的线程调用
wait()时,释放锁并进入_WaitSet -
当其他线程调用
notify()时,_WaitSet中的线程被移到_EntryList重新竞争
第五幕:完整通关秘籍——锁升级流程图

最终决战:锁的选择策略
偏向锁:Java 15+ 默认禁用,因为维护成本高
轻量级锁:适用于短时间、低竞争的同步
重量级锁:高并发场景的最终选择
现代最佳实践:
// 对于高并发场景,往往直接使用更高级的并发工具
private final AtomicInteger count = new AtomicInteger(0);public void addCount() {count.incrementAndGet(); // CAS操作,比synchronized更轻量
}
闯关成功总结
-
synchronized不是一成不变的 - 它会根据竞争情况智能升级
-
锁的代价从低到高:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
-
对象头是锁状态的记录者 - Mark Word的变化就是锁的进化史
-
Monitor是最终的守护者 - 通过队列管理阻塞线程
现在,当你在代码中写下 synchronized 时,脑海中应该浮现出这幅完整的锁侠进化图。恭喜你,已经从 synchronized 小白晋级为锁原理高手!
记住:理解底层,是为了写出更好的上层代码! 🚀
