核心速览

无论是写在方法签名上(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 标志:

  1. 如果有该标志:JVM 会在调用方法之前,自动隐式地执行 monitorenter(锁住 thisClass 对象)
  2. 执行方法体
  3. 方法结束(无论正常返回还是抛异常):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++;
        }
    }
}

一键三连,让我的信心像气球一样膨胀!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐