2025-05-19 13:53

双重检查锁的懒汉式方式实现单例模式中volatile的作用

王姐姐

Java后端

(49)

(0)

收藏

在双重检查锁单例模式中,‌不加 volatile 关键字可能导致线程安全问题‌,具体问题场景如下:

1. 指令重排序导致对象未完全初始化‌

当 instance = new Singleton() 时,JVM 会执行以下步骤:

分配内存空间

初始化对象(调用构造方法)

将引用指向内存地址

无 volatile 时‌:

编译器和 CPU 可能对指令进行重排序,可能导致步骤 ‌3 先于步骤 2 执行‌。此时,其他线程在第一次判空检查 if (instance == null) 时,可能获取到一个 ‌未完成初始化‌ 的 instance 对象。

示例场景‌:

线程 A 执行 instance = new Singleton(),但指令重排序导致引用已赋值(步骤3),对象尚未初始化(步骤2未完成)。

线程 B 进入 getInstance(),判断 instance != null,直接返回未初始化的对象并使用,导致程序异常。

2. 可见性问题(早期 Java 版本)‌

在 JDK 5 之前,Java 内存模型(JMM)未强制要求同步块内的变量修改对其他线程立即可见。即使线程 A 在同步块中完成实例初始化,若不加 volatile,线程 B 可能因本地缓存未刷新,仍认为 instance 为 null,从而导致重复创建实例。

示例场景‌:

线程 A 在同步块中创建 instance 并退出同步块,但修改未及时刷新到主内存。

线程 B 进入同步块前的第一次检查 if (instance == null),因本地缓存未更新仍判断为 null,进入同步块创建第二个实例。

总结‌

场景                                                 问题描述                                                            解决方案

指令重排序‌                         其他线程获取到未初始化的对象实例                             volatile 禁止指令重排序

可见性问题(早期JVM)         多线程环境下实例状态未及时同步,导致重复创建         volatile 强制内存可见性

代码验证‌

public class Singleton {
    private static Singleton instance;  // 未加 volatile
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // 可能导致未初始化对象被提前暴露
                }
            }
        }
        return instance;
    }
}

结论‌:

在并发环境下,‌必须使用 volatile‌ 修饰单例实例,以解决指令重排序和可见性问题。

0条评论

点击登录参与评论