synchronized
是 Java 中用于实现线程同步的关键字,它提供了一种互斥访问共享资源的机制,确保多个线程在访问临界区(Critical Section)时不会发生数据竞争(Data Race),从而保证线程安全。
1. synchronized
的作用
synchronized
的主要作用是:
互斥访问(Mutual Exclusion):确保同一时间只有一个线程可以进入同步代码块或方法,防止多个线程同时修改共享数据。
可见性(Visibility):确保线程对共享变量的修改对其他线程立即可见(类似于
volatile
的可见性保证)。有序性(Ordering):防止指令重排序,确保同步代码块内的操作按预期顺序执行。
2. synchronized
的底层原理
synchronized
的底层实现依赖于 JVM 的 Monitor(监视器)机制 和 Monitor Enter/Monitor Exit 指令。
2.1. Monitor(监视器)机制
每个 Java 对象都有一个 Monitor(监视器锁),也称为 内置锁(Intrinsic Lock) 或 对象锁
当线程进入
synchronized
代码块时,会尝试 获取该对象的 Monitor 锁(MonitorEnter
)如果锁已被其他线程持有,则当前线程会 阻塞,直到锁被释放
当线程退出
synchronized
代码块时,会 释放 Monitor 锁(MonitorExit
)
2.2. JVM 指令
synchronized
方法或代码块在字节码层面会被编译为:
monitorenter
(进入同步块,获取锁)
monitorexit
(退出同步块,释放锁)
如果发生异常,JVM 会确保 monitorexit
被执行,防止死锁。
3. synchronized
的使用方式
3.1. 同步实例方法(同步当前对象)
public class Counter {
private int count = 0;
// 同步实例方法,锁的是当前对象(this)
public synchronized void increment() {
count++;
}
}
锁对象:当前实例对象(
this
)。作用范围:整个方法体。
适用场景:当多个线程访问同一个对象的同步方法时,同一时间只有一个线程可以执行该方法。
3.2. 同步静态方法(同步类对象)
public class Counter {
private static int count = 0;
// 同步静态方法,锁的是当前类的 Class 对象(Counter.class)
public static synchronized void increment() {
count++;
}
}
锁对象:当前类的
Class
对象(如Counter.class
)。作用范围:整个静态方法体。
适用场景:当多个线程访问同一个类的静态同步方法时,同一时间只有一个线程可以执行该方法。
3.3. 同步代码块(灵活控制锁对象)
public class Counter {
private int count = 0;
private final Object lock = new Object(); // 自定义锁对象
public void increment() {
synchronized (lock) { // 同步代码块,锁的是 lock 对象
count++;
}
}
}
锁对象:可以是任意对象(如
this
、Class
对象、自定义对象)。作用范围:仅限于
synchronized
块内的代码。适用场景:当只需要同步部分代码时,使用同步代码块可以提高性能(减少锁的粒度)。
4. volatile 与synchronized的核心功能对比
特性 |
|
|
---|---|---|
原子性 |
✅ 保证代码块或方法的原子性(复合操作) |
❌ 仅保证单次读/写操作的原子性(如 |
可见性 |
✅ 保证线程修改后对其他线程立即可见 |
✅ 保证线程修改后对其他线程立即可见 |
有序性 |
✅ 防止指令重排序(同步块内的操作顺序固定) |
✅ 防止指令重排序( |
阻塞机制 |
✅ 线程竞争锁时会阻塞,直到获取锁 |
❌ 不会导致线程阻塞 |
适用场景 |
复杂同步(如多步操作、临界区保护) |
单一变量可见性(如状态标志) |
volatile
性能更高:因为它不涉及锁竞争和线程阻塞,仅通过内存屏障保证可见性。
synchronized
在低竞争时优化后性能接近volatile
,但在高竞争时会显著变慢。