synchronized 与 volatile
1. synchronized
Section titled “1. synchronized”1.1. 三种使用方式
Section titled “1.1. 三种使用方式”// 方式一:修饰实例方法 —— 锁是当前对象实例(this)public synchronized void instanceMethod() { ... }
// 方式二:修饰静态方法 —— 锁是 Class 对象(类级别,所有实例共享)public static synchronized void staticMethod() { ... }
// 方式三:修饰代码块 —— 锁是指定对象(粒度最细,推荐)synchronized (lockObj) { ... }1.2. 底层原理
Section titled “1.2. 底层原理”字节码层面:monitorenter / monitorexit 指令,基于对象头的 Mark Word。
锁升级过程(JDK 6 优化后):
无锁 ──► 偏向锁 ──► 轻量级锁(CAS 自旋)──► 重量级锁(OS mutex) 单线程 低竞争 高竞争 无 CAS 避免挂起 线程挂起/唤醒- 偏向锁:第一个获取锁的线程,将线程 ID 写入 Mark Word,后续同一线程直接进入,无需 CAS;
- 轻量级锁:有第二个线程竞争时升级,通过 CAS 自旋尝试获取,避免线程切换开销;
- 重量级锁:自旋超过阈值后升级,线程挂起进入 OS 等待队列。
1.3. synchronized 的三个保证
Section titled “1.3. synchronized 的三个保证”| 保证 | 原理 |
|---|---|
| 原子性 | monitorenter / monitorexit 确保临界区互斥执行 |
| 可见性 | 进入同步块时从主内存刷新,退出时写回主内存 |
| 有序性 | unlock happens-before 后续 lock,临界区内有序 |
2. volatile
Section titled “2. volatile”2.1. 两个保证,一个不保证
Section titled “2.1. 两个保证,一个不保证”保证可见性 ✅ 写操作立刻 flush 到主内存,读操作总从主内存 load保证有序性 ✅ 插入内存屏障,禁止跨屏障的指令重排序保证原子性 ❌ i++ 仍不是原子操作,需要 AtomicInteger2.2. 内存屏障
Section titled “2.2. 内存屏障”volatile 通过插入 4 种内存屏障实现禁止重排序:
| 屏障类型 | 效果 |
|---|---|
StoreStore | volatile 写前,确保之前的普通写已完成 |
StoreLoad | volatile 写后,确保写对后续读可见(最重) |
LoadLoad | volatile 读后,确保后续读不被提前 |
LoadStore | volatile 读后,确保后续写不被提前 |
2.3. 典型应用场景
Section titled “2.3. 典型应用场景”场景一:状态标志位
private volatile boolean running = true;
public void stop() { running = false; }
// 另一个线程while (running) { doWork(); }场景二:双重检查锁(DCL)单例
public class Singleton { // volatile 是关键!防止对象"半初始化"被其他线程读到 private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { // 第一次检查(无锁) synchronized (Singleton.class) { if (instance == null) { // 第二次检查(持锁) instance = new Singleton(); } } } return instance; }}3. synchronized vs volatile 对比
Section titled “3. synchronized vs volatile 对比”| 对比项 | synchronized | volatile |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 原子性 | ✅(代码块内) | ❌ |
| 有序性 | ✅ | ✅ |
| 阻塞线程 | 是 | 否 |
| 性能开销 | 较大(锁竞争、线程切换) | 较小(仅内存屏障) |
| 适用场景 | 复合操作、临界区保护 | 单变量状态标志、DCL |
| 编译器优化 | 不会优化掉同步代码 | 禁止寄存器缓存该变量 |
4. 面试高频问题
Section titled “4. 面试高频问题”Q:volatile 能保证原子性吗?
A:不能。volatile int i = 0; i++ 仍然是非原子操作(read-modify-write 三步)。需要保证原子性应使用 AtomicInteger 或 synchronized。
Q:synchronized 锁升级能降级吗?
A:基本不能。轻量级锁和重量级锁都无法降级;偏向锁在发生竞争时会撤销,但撤销后升级为轻量级锁,不是降回无锁态。
Q:synchronized 修饰的方法,子类重写后还具有同步性吗?
A:不一定。synchronized 不是方法签名的一部分,子类重写后默认不加锁,需要显式加上 synchronized 关键字。
Q:一个线程持有对象锁,另一个线程能调用该对象的非同步方法吗?
A:可以。synchronized 只保护有锁的方法/代码块,非同步方法不受影响,可以被任意线程随时调用。
Q:DCL 中第一次 null 检查有什么意义?
A:性能优化。instance 初始化完成后,大多数调用直接通过第一次检查返回,完全不进入 synchronized 块,避免了锁的开销。只有 instance == null 时才竞争锁。