volatile通过禁止指令重排序(强制“分配内存→初始化→赋值”顺序)和保证可见性(读写均直达主内存),确保其他线程获取的instance必为完全初始化对象,从而堵住dcl中对象半初始化漏洞。

volatile 如何堵住对象半初始化漏洞
在双重检查锁定(DCL)中,instance = new Singleton() 看似原子操作,实际分三步: - 分配内存空间 - 调用构造函数初始化对象 - 将引用赋值给 instance 变量
JVM 和 CPU 可能将第 2 步和第 3 步重排序为「1→3→2」。此时 instance 已非 null,但对象尚未完成初始化。另一个线程读到非 null 的 instance,直接返回并调用其方法,就会触发空指针或状态异常。
volatile 的关键作用就是禁止这种重排序:它在写操作(即 instance 赋值)前后插入内存屏障,强制构造函数执行完毕(步骤 2 完成)后,才能执行步骤 3(引用赋值)。这样其他线程看到的 instance,必然是已完全初始化的对象。
volatile 同时保障可见性,避免重复创建
没有 volatile 时,线程 A 创建完 instance 并写入本地缓存,可能迟迟不刷回主内存;线程 B 检查 instance == null 时,读的是自己缓存里的旧值(null),于是也进入同步块创建新实例——单例被破坏。
volatile 让每次对 instance 的读都从主内存加载,每次写都立即刷新到主内存。这样: - 线程 B 能及时看到线程 A 写入的非 null 值,跳过重复创建 - 所有线程读到的都是同一份最新值,不会因缓存不一致而误判
必须配合两次判空与 synchronized 使用
volatile 单独不能保证线程安全,它只解决可见性和有序性,不提供原子性。DCL 正确结构缺一不可: - 第一次判空(无锁快速路径),减少同步开销 - synchronized 块保证创建动作互斥 - 第二次判空(加锁后再次确认),防止多个线程同时进入同步块重复初始化 - instance 字段用 volatile 修饰,封堵重排与可见性漏洞
常见错误写法及后果
以下写法看似相似,实则危险: - 漏掉 volatile → 可能返回未初始化对象,运行时崩溃 - 只做一次判空 → 高并发下仍可能创建多个实例 - 把 synchronized 加在 getInstance 方法上 → 失去性能优势,变成串行获取 - 构造函数未私有 → 外部可 new 实例,彻底破坏单例语义
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











