跳转到内容

synchronized 与 volatile

// 方式一:修饰实例方法 —— 锁是当前对象实例(this)
public synchronized void instanceMethod() { ... }
// 方式二:修饰静态方法 —— 锁是 Class 对象(类级别,所有实例共享)
public static synchronized void staticMethod() { ... }
// 方式三:修饰代码块 —— 锁是指定对象(粒度最细,推荐)
synchronized (lockObj) { ... }

字节码层面:monitorenter / monitorexit 指令,基于对象头的 Mark Word。

锁升级过程(JDK 6 优化后):

无锁 ──► 偏向锁 ──► 轻量级锁(CAS 自旋)──► 重量级锁(OS mutex)
单线程 低竞争 高竞争
无 CAS 避免挂起 线程挂起/唤醒
  • 偏向锁:第一个获取锁的线程,将线程 ID 写入 Mark Word,后续同一线程直接进入,无需 CAS;
  • 轻量级锁:有第二个线程竞争时升级,通过 CAS 自旋尝试获取,避免线程切换开销;
  • 重量级锁:自旋超过阈值后升级,线程挂起进入 OS 等待队列。
保证原理
原子性monitorenter / monitorexit 确保临界区互斥执行
可见性进入同步块时从主内存刷新,退出时写回主内存
有序性unlock happens-before 后续 lock,临界区内有序
保证可见性 ✅ 写操作立刻 flush 到主内存,读操作总从主内存 load
保证有序性 ✅ 插入内存屏障,禁止跨屏障的指令重排序
保证原子性 ❌ i++ 仍不是原子操作,需要 AtomicInteger

volatile 通过插入 4 种内存屏障实现禁止重排序:

屏障类型效果
StoreStorevolatile 写前,确保之前的普通写已完成
StoreLoadvolatile 写后,确保写对后续读可见(最重)
LoadLoadvolatile 读后,确保后续读不被提前
LoadStorevolatile 读后,确保后续写不被提前

场景一:状态标志位

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;
}
}
对比项synchronizedvolatile
可见性✅✅
原子性✅(代码块内)❌
有序性✅✅
阻塞线程是否
性能开销较大(锁竞争、线程切换)较小(仅内存屏障)
适用场景复合操作、临界区保护单变量状态标志、DCL
编译器优化不会优化掉同步代码禁止寄存器缓存该变量

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 时才竞争锁。