Java synchronized 关键字四种用法详解与原理

桃夭
桃夭
发布时间:2026-10-13 | 2人浏览过
原创

在 Java 多线程编程中,synchronized 是保证共享数据竞争安全性的核心机制。它通过互斥锁确保同一时刻最多只有一个线程执行关键代码。本文将详细解读 synchronized 的四种具体应用形态——修饰实例方法、修饰静态方法、针对实例对象的代码块以及针对类对象的代码块,帮助开发者准确选择锁策略。

synchronized 的基本作用

synchronized 是 Java 语言的关键字,当用于修饰方法或代码块时,能够保证在同一时刻最多只有一个线程执行该段代码。这种互斥性是实现线程同步的基础。

用法一:修饰实例方法(实例锁)

当 synchronized 关键字放在范围操作符(如 public)之后、返回类型声明之前时,用于修饰实例方法。此时,线程获得的是实例对象锁(this)。这意味着一次只能有一个线程进入该方法,其他线程需排队等候。

public synchronized void synMethod() {
    // 方法体,同一时刻仅一个线程可执行此方法的实例版本
}

注意:如果存在另一个同名实例 P2,P1 上的同步并不限制 P2 的其他线程调用该方法。即锁绑定于具体的对象实例而非整个类的所有实例。这等同于在方法内部使用 synchronized (this)。

用法二:修饰实例代码块(自定义对象锁)

在代码块中使用 synchronized 后跟括号,括号内是一个具体的对象。此时线程获得该指定对象的锁。这是一种比较粗粒度的做法,因为它锁定了整个对象,即便代码块只涉及对象的部分资源。

public int synMethod(int a1) {
    synchronized(a1) {
        // 一次只有一个拥有 a1 锁的线程能进入
    }
}

细粒度优化:为了解锁整个对象带来的性能问题,可以使用独立的虚拟对象作为锁,实现更细致的控制:

class FineGrainLock {
    MyMemberClass x, y;
    Object xlock = new Object(), ylock = new Object();

    public void foo() {
        synchronized(xlock) {
            // 访问 x
        }
        // 非共享资源处理
        synchronized(ylock) {
            // 访问 y
        }
    }
}

用法三:修饰代码块指定类对象(类锁的一种表现)

在 synchronized 括号中直接引用某个类的 Class 对象,可以获得该类的全局锁。这种方式常用于需要跨实例进行状态协调的场景。

class ArrayWithLockOrder {
    private static long num_locks = 0;
    private long lock_order;

    public ArrayWithLockOrder(int[] a) {
        synchronized(ArrayWithLockOrder.class) {
            num_locks++;
            lock_order = num_locks;
        }
    }
    // ... 其他方法
}

若线程进入此类锁定的代码块,则该类中的所有相关操作将被阻塞。这在含有静态变量需要同步时非常常见。

用法四:修饰静态方法(类锁)

当 synchronized 用于 static 函数时,锁定的是Class 对象(类锁),而非某个具体实例的对象锁。这意味着同一个类的不同对象 Obj1 和 Obj2,若分别调用各自的静态同步方法和非静态同步方法,两者之间不会构成同步,因为锁是不同的。

public class Foo {
    public synchronized static void method1() {
        // 锁定的是 Foo.class
    }
    public void method2() {
        synchronized(Foo.class) {
            // 同样锁定的是 Foo.class,与 static 方法互斥
        }
    }
}

关键点:
- synchronized static 锁定 Class。
- synchronized instance 锁定 this 对象实例。
两者锁级别不同,无法互相阻止对方执行。

最佳实践与注意事项

1. 优先使用零长度 byte 数组充当锁:
如果需要一个明确的私有锁对象但没有业务含义的对象可用,推荐使用 private byte[] lock = new byte[0];。相比于创建 new Object(),零字节数组在 JVM 层面的开销更小,生成的字节码指令更少。

private byte[] lock = new byte[0];
Public void method(){ 
    synchronized(lock) { } 
} 

2. 防死锁排序:
在需要同时锁定多个对象的场景中(如数组求和示例),务必按照统一的顺序加锁。例如根据对象的固定 ID 或生成顺序来决定锁定先后,防止两个线程交叉持有对方需要的锁导致死锁。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表