synchronized方法靠acc_synchronized标志位,jvm自动加锁解锁;代码块靠monitorenter/monitorexit指令对,手动控制锁范围;二者最终都操作对象头的monitor。

synchronized 方法和 synchronized 代码块,表面写法不同,底层行为也不同——但新手不用怕,拆开字节码一看就清楚了。
核心一句话:方法级靠“标志位”,代码块级靠“指令对”。
方法上加 synchronized → 字节码里只多一个标志
写法示例:
public synchronized void method() {
count++;
}
反编译后(javap -v)关键片段:
public synchronized void method();
flags: ACC_PUBLIC, ACC_SYNCHRONIZED ← 就是这行!
Code:
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
- 没有
monitorenter,也没有monitorexit - JVM 在调用这个方法前,自动检查
ACC_SYNCHRONIZED标志 - 有标志 → 自动获取
this的 Monitor;方法结束(无论正常还是异常)→ 自动释放
✅ 优点:简洁、无侵入
❌ 缺点:锁粒度大,整个方法体都被包住,哪怕只有两行要同步,其余几十行也得排队
代码块里加 synchronized → 字节码里真有两条指令
写法示例:
public void block() {
synchronized (this) {
count++;
}
}
反编译后关键片段:
public void block();
flags: ACC_PUBLIC
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter ← 这里开始抢锁
4: aload_0
5: dup
6: getfield #2 // Field count:I
9: iconst_1
10: iadd
11: putfield #2 // Field count:I
14: aload_1
15: monitorexit ← 正常退出,放锁
16: goto 24
19: astore_2
20: aload_1
21: monitorexit ← 异常出口,也放锁
22: aload_2
23: athrow
24: return
-
monitorenter出现在同步块入口 -
monitorexit至少出现两次:一次在正常结束处,一次在异常处理路径(Exception table中隐含) - 锁对象是
this,但你可以换成任意对象,比如synchronized(lock),字节码里aload_1加载的就是那个lock
✅ 优点:锁范围精准、可选任意锁对象、性能更可控
❌ 缺点:多两行代码,需手动保证成对(不过编译器已帮你插好,你不用手写指令)
它们锁的到底是什么?统一答案是:对象头里的 Monitor
- 无论是方法还是代码块,最终操作的都是 目标对象的对象头(Mark Word)
- 实例方法 → 锁
this→ 操作this对象头 - 静态方法 → 锁
MyClass.class→ 操作 Class 对象头 - 代码块
synchronized(obj)→ 锁obj→ 操作obj对象头
JVM 不关心你写的是方法还是块,它只认:
-
ACC_SYNCHRONIZED标志 → 给整个方法加 Monitor -
monitorenter/monitorexit→ 给某段指令加 Monitor
而且,Monitor 的状态(偏向锁 / 轻量锁 / 重量锁)都记录在对象头的 Mark Word 里,跟写法无关。
怎么动手验证?三步搞定
- 写一个最简类,含一个
synchronized方法 + 一个synchronized(this)块 -
javac YourClass.java编译 -
javap -c -v YourClass.class查看字节码- 找
flags:行看有没有ACC_SYNCHRONIZED - 找
monitorenter和monitorexit出现位置和次数
- 找
你会发现:
- 方法签名带
synchronized→ 只有标志,没指令 - 代码块 → 必有
monitorenter,且至少两个monitorexit
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











