volatile 的作用

在上篇博客中,已经提到 volatile 保证内存可见性的作用,这次加上 volatile 禁止指令重排序的作用。

java 复制代码
class SingletonLazy {
    private static volatile SingletonLazy instance = null;
    
    public static  SingletonLazy getInstance() {
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    
    private SingletonLazy() {
        
    }
}

如果没有 volatile,这段代码在多线程环境下可能返回一个"未完全初始化"的对象,导致严重 bug!

🔍 为什么需要它?------ 深入分析"对象创建"过程

new SingletonLazy() 看似一行代码,但在 JVM 底层其实分为 3 步:

  1. 分配内存空间(为 SingletonLazy 对象分配堆内存)

  2. 调用构造方法(初始化对象的字段、执行构造逻辑)

  3. 将引用赋值给 instanceinstance = 引用地址

⚠️ 问题:JVM 可能重排序!

在没有 volatile 的情况下,JVM 或 CPU 可能将步骤 2 和 3 重排序,变成:

  1. 分配内存

  2. 将引用赋值给 instance(此时对象尚未初始化!)

  3. 调用构造方法(延迟执行)

💥 这就是 "指令重排序"(Instruction Reordering) 优化。

🧨 多线程下的灾难场景(无 volatile

假设有两个线程 T1 和 T2:

时间 T1(正在创建实例) T2(调用 getInstance()
1 执行 new: ① 分配内存 ③ 先赋值 instance = 地址(重排序!) ---
2 --- 检查 if (instance == null)false!(因为 instance 已非 null)
3 --- 直接 return instance(拿到一个未初始化的对象!)
4 ② 执行构造方法(太晚了!) 使用 instance访问未初始化的字段 → NullPointerException / 数据错误!

❌ 这就是著名的 "DCL 失效问题"(在 Java 5 之前无法安全修复)。

volatile 如何解决这个问题?

当你声明:

java 复制代码
private static volatile SingletonLazy instance = null;

volatile 提供两个关键保障:

  1. 禁止重排序
  • JVM 保证 instance = new SingletonLazy() 的三步操作不会被重排序。

  • 必须严格按照:分配内存 → 初始化 → 赋值 的顺序执行。

  1. 保证可见性
  • 当 T1 执行 instance = ... 后,T2 立即能看到最新值(不会读到缓存中的 null)。

✅ 结果:T2 要么看到 instance == null(继续等待锁),

要么看到一个完全初始化好的对象。

❌ 如果去掉 volatile 会怎样?

java 复制代码
private static SingletonLazy instance = null; // 没有 volatile!
  • 单线程:一切正常。

  • 多线程:有极小概率(但确实存在)返回未初始化对象。

  • Bug 难以复现(依赖 CPU/JVM/时机),但一旦发生,后果严重(崩溃、数据错乱)。

🐞 这类 bug 被称为 "Heisenbug"(观测时消失,不观测时出现)。

相关推荐
一灯架构4 小时前
90%的人答错!一文带你彻底搞懂ArrayList
java·后端
Y4090015 小时前
【多线程】线程安全(1)
java·开发语言·jvm
布局呆星5 小时前
SpringBoot 基础入门
java·spring boot·spring
风吹迎面入袖凉5 小时前
【Redis】Redisson的可重入锁原理
java·redis
w6100104665 小时前
cka-2026-ConfigMap
java·linux·cka·configmap
语戚6 小时前
力扣 968. 监控二叉树 —— 贪心 & 树形 DP 双解法递归 + 非递归全解(Java 实现)
java·算法·leetcode·贪心算法·动态规划·力扣·
quxuexi6 小时前
网络通信安全与可靠传输:从加密到认证,从状态码到可靠传输
java·安全·web
hrhcode7 小时前
【java工程师快速上手go】二.Go进阶特性
java·golang·go
小碗羊肉9 小时前
【从零开始学Java | 第三十一篇下】Stream流
java·开发语言