Java synchronized 关键字四种用法详解与原理
在 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 或生成顺序来决定锁定先后,防止两个线程交叉持有对方需要的锁导致死锁。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









