Java并发:synchronized 方法 vs 代码块,都锁住了什么?
✅ 核心速览:
无论是写在方法签名上(public synchronized void method()),还是写在代码块里(synchronized(this) { ... }),JVM 最终操作的都是当前实例对象(this)的对象头(Mark Word)。
| 维度 | 同步方法 synchronized void m() |
同步代码块 synchronized(obj) |
|---|---|---|
| 底层实现 | 方法标志位 ACC_SYNCHRONIZED |
字节码指令 monitorenter/exit |
| 锁住的对象 | 实例方法锁 this;静态方法锁 Class |
括号里指定的任意对象 (obj) |
| 操作目标 | 依然是对象头 (Mark Word) |
依然是对象头 (Mark Word) |
| 锁的粒度 | 粗(整个方法) | 细(仅限代码块) |
| 推荐程度 | 简单场景可用 | ✅ 推荐(性能更好,更安全) |
在 JVM 眼里,根本没有"同步方法"这个概念,它只认"同步代码块"。所以下述两种写法操作的目标依然是那个对象的内存块(对象头)。如果对synchronized的演进感兴趣可以阅读,传送门:synchronized演进,从JDK 1.6 锁升级到 JDK 24 重构
// 锁实例方法
synchronized void m() ≈ synchronized(this) { ... }
// 锁静态方法
synchronized static void m() ≈ synchronized(MyClass.class) { ... }
不同的包装器
虽然锁的是同一个对象,但 Java 编译器(javac)在生成 .class 文件时,对这两种写法采用了不同的"包装策略"。
同步代码块(显式包装)
当写 synchronized(obj) 时,编译器会在字节码中显式地插入两条指令:
| 指令 | 作用 |
|---|---|
monitorenter |
放在代码块开头,尝试获取锁 |
monitorexit |
放在代码块结尾(及异常处理出口),释放锁 |
字节码示意:
// 对应 synchronized(this)
aload_0 // 1. 把 this 对象推入栈顶
monitorenter // 2. 尝试获取 this 的 Monitor(操作对象头)
// ... 业务代码 ...
monitorexit // 3. 释放 Monitor
锁的粒度精确控制,只在花括号内持有锁。
同步方法(隐式标志)
当写 public synchronized void method() 时,编译器不会在方法内部插入 monitorenter 指令。相反,它会在方法定义的元数据中,设置一个 ACC_SYNCHRONIZED 标志位。
字节码示意:
// 方法定义头
public synchronized void method()
flags: ACC_SYNCHRONIZED // ← 关键标志位
// 方法体内部完全干净,没有 monitorenter
getfield ...
return
当线程准备调用这个方法时,JVM 会检查方法的 ACC_SYNCHRONIZED 标志:
- 如果有该标志:JVM 会在调用方法之前,自动隐式地执行
monitorenter(锁住this或Class对象) - 执行方法体
- 方法结束(无论正常返回还是抛异常):JVM 会自动隐式地执行
monitorexit
虽然字节码看起来不一样(一个是显式指令,一个是标志位),但最终触发锁机制时,JVM 依然是去修改
this对象的对象头(Mark Word)。
区别
synchronized 修饰方法,只是 Java 编译器提供的一个语法糖。在 JVM 运行时,它依然会退化为"获取 this 对象 Monitor"的操作,操作的依然是那个对象的对象头(Mark Word)。
1、锁粒度不同
| 写法 | 锁的范围 | 适用场景 |
|---|---|---|
| 同步方法 | 整个方法(从第一行到 return) |
简单场景,整个方法都需同步 |
| 同步代码块 | 仅限花括号内 | 只有部分代码涉及共享资源时,缩小锁粒度,提高并发性能 |
// ❌ 同步方法:锁粒度较粗
public synchronized void process() {
// 1. 非共享资源操作(也被锁住了,浪费)
prepareData();
// 2. 共享资源操作(真正需要同步的部分)
updateSharedResource();
// 3. 后续处理(也被锁住了,浪费)
notifyListeners();
}
// ✅ 同步代码块:锁粒度精确
public void process() {
prepareData(); // 无锁执行
synchronized(this) {
updateSharedResource(); // 仅此处加锁
}
notifyListeners(); // 无锁执行
}
锁的对象不同
| 写法 | 可锁对象 | 说明 |
|---|---|---|
| 同步方法 | 实例方法 → this静态方法 → Class 对象 |
固定,无法自定义 |
| 同步代码块 | 任意对象 | ✅ 灵活,可指定私有锁对象 |
最佳实践:不要用 this 做锁(因为外部代码也可以锁住你的 this,导致死锁),而是用一个私有的 lock 对象:
public class SafeCounter {
// 私有锁对象,外部无法访问
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized(lock) { // ✅ 安全:锁对象不暴露
count++;
}
}
}
更多推荐




所有评论(0)