跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

怎么利用 Double-Checked Locking 模式结合 volatile 实现高性能单例的初始化

必须用volatile修饰静态实例变量,否则多线程下可能拿到未初始化完成的对象;因为JVM创建对象的三步(分配内存、调用构造函数、赋值引用)可能重排序,导致其他线程看到半初始化状态。 必须用
volatile
修饰静态实例变量,否则多线程下可能拿到未初始化完成的对象。 为什么
instance = new Singleton()
会出问题 JVM 对对象创建的三步操作(分配内存、调用构造函数、赋值引用)可能重排序。没有
volatile
时,线程 A 可能先执行“分配内存 + 赋值引用”,再执行“构造函数”,此时线程 B 看到
instance != null
就直接返回,但实际
Singleton
的字段还是默认值(如
null
、
0
),后续调用会 NPE 或逻辑错误。 加
volatile
后,写操作会插入内存屏障,禁止构造函数调用被重排到赋值之后,同时保证其他线程读到该变量时能看到完整的初始化结果。
synchronized
块里为什么还要二次判空 第一次判空在同步外,是为了避免每次调用
getInstance()
都进锁——这是性能关键点;第二次判空在锁内,是因为多个线程可能同时卡在第一次检查后、抢到锁前,若不二次检查,就会重复初始化。 不加第二次检查 → 多个线程都执行
new Singleton()
→ 多个实例 不加
volatile
→ 即使只一个线程执行
new
,其他线程也可能看到半初始化状态 锁对象必须是类本身(如
Singleton.class
),不能用
this
,因为静态方法里没有实例 典型实现中容易漏掉的细节 常见错误不是语法错,而是语义疏忽: 构造函数没设为
private
→ 外部可
new
破坏单例
instance
没加
volatile
→ 并发下不可靠(JDK 5+ 才真正支持
volatile
的内存语义) 把
synchronized
加在方法上(
public static synchronized Singleton getInstance()
)→ 完全失去 DCL 的性能优势 初始化逻辑里抛异常但没处理 → 第二次调用仍会尝试创建,可能反复失败 示例代码核心片段:
private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 构造函数必须无参且私有 } } } return instance; }
真正难的是理解「可见性」和「重排序」不是理论问题,而是在高并发压测时才会暴露的偶发崩溃。哪怕测试跑一万次不失败,也不能说明它安全——只要没
volatile
,就永远存在风险。

相关文章