volatile解决指令重排序导致未初始化对象被访问的问题,禁止“分配内存→引用赋值→初始化字段”重排序,保证可见性与有序性;CAS通过AtomicReference.compareAndSet实现无锁原子初始化,避免synchronized开销。

通过 volatile 配合 CAS(Compare-And-Swap)实现无锁单例,核心在于**避免双重检查锁定(DCL)中因指令重排序导致的未初始化对象被其他线程访问的问题**,同时利用 CAS 原子操作替代 synchronized,进一步降低锁开销。Java 中标准写法是“双重检查 + volatile + lazySet/CAS 初始化”,但需注意:纯 CAS 实现单例通常不直接用于构造器调用,而是配合原子引用(如 AtomicReference)管理实例状态。
volatile 解决什么问题?
在双重检查单例中,若不用 volatile 修饰单例引用,JVM 可能将对象构造过程(分配内存 → 初始化字段 → 引用赋值)重排序为「分配内存 → 引用赋值 → 初始化字段」。此时另一个线程可能看到非 null 的引用,但对象字段尚未初始化,导致 NPE 或逻辑错误。volatile 禁止该重排序,并保证后续读操作能看到完整的初始化结果,提供可见性与有序性保障。
CAS 如何替代同步块?
不用 synchronized,改用 AtomicReference 的 compareAndSet(null, newInstance) 实现原子初始化:
- 线程 A 尝试用 CAS 设置实例,成功则返回新实例;
- 线程 B 同时尝试,CAS 失败(因为引用已非 null),转而直接返回已设置的实例;
- 无需阻塞等待,也无锁升级开销,适合高并发读多写少场景。
典型实现代码(推荐)
以下为线程安全、无锁、无重排序风险的写法:
public class Singleton {
private static final AtomicReference<singleton> INSTANCE = new AtomicReference();
<pre class="brush:php;toolbar:false;">private Singleton() {}
public static Singleton getInstance() {
Singleton instance = INSTANCE.get();
if (instance == null) {
// 第一次竞争:用 CAS 尝试初始化
instance = new Singleton(); // 构造本身是非原子的,但只执行一次
if (INSTANCE.compareAndSet(null, instance)) {
// CAS 成功:当前线程完成初始化,返回
return instance;
} else {
// CAS 失败:说明其他线程已设置,取最新值
return INSTANCE.get();
}
}
return instance;
}
}
⚠️ 注意:
- new Singleton() 在 CAS 外构造,存在“对象创建后未被使用”的浪费(极小概率);更优做法是把构造逻辑放进 CAS 循环内(配合自旋),或采用 lazySet + 标志位组合,但复杂度上升;
- 此实现仍依赖 AtomicReference 内部的 volatile 语义,本质仍是 volatile + CAS 协同;
- 不适用于需要传参的构造场景(如带配置的单例),此时建议回归静态内部类或 enum 方式。
为什么不直接用 Unsafe CAS 手动操作?
虽然 Unsafe.compareAndSwapObject 更底层,但:
- 需要获取字段偏移量,易出错且不跨版本兼容;
- 缺少高级封装(如自动重试、内存屏障控制);
- AtomicReference 已经对 volatile + CAS 做了最佳实践封装,可读性与安全性更高;
- JDK 9+ 对 Unsafe 限制加强,不推荐业务代码直接使用。










