双重检验锁的单例模式在高并发下的可见性问题

双重检查锁定(DCL) 单例模式中,如果没有使用 volatile 修饰实例变量,可能会因为指令重排序导致其他线程获取到未完全初始化的对象,从而引发可见性问题。

问题复现

java 复制代码
public class Singleton {
    private static Singleton instance;  // 缺少 volatile
    public static Singleton getInstance() {
        if (instance == null) {               // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {       // 第二次检查
                    instance = new Singleton(); // 问题所在
                }
            }
        }
        return instance;
    }
}

instance = new Singleton(); 这一行在底层并非原子操作,它大致包含三个步骤:

  1. 分配内存 -- 为 Singleton 对象分配堆内存。
  2. 初始化对象 -- 调用构造器,对成员变量赋初值。
  3. 将引用指向内存地址 -- 让 instance 指向这块内存。

指令重排序的影响

在没有 volatile 保证有序性的情况下,编译器或 CPU 可能会将第 2 步(初始化)和第 3 步(引用赋值)颠倒顺序。于是实际执行顺序变为:

    1. 分配内存
    1. 将 instance 指向内存地址(此时内存中的对象还未初始化)
    1. 初始化对象

可见性问题的发生过程

  1. 线程 A 进入 getInstance(),发现 instance == null,获得锁,执行 instance = new Singleton();。
    由于重排序,A 先分配内存并让 instance 指向该地址(但尚未调用构造器初始化)。此时 A 可能被挂起或时间片用完。
  2. 线程 B 调用 getInstance(),第一次检查 instance。
    instance 已经不为 null(因为 A 已经赋值了地址),于是 B 直接返回这个"半成品"对象。
  3. 线程 B 使用该对象,访问其成员变量时,由于对象尚未完成初始化,可能读取到默认值(如 0、null) 而非构造器中赋予的值,造成程序行为异常,甚至崩溃。

解决方案

使用 volatile 修饰 instance 变量 ,禁止指令重排序,确保 instance 引用的赋值发生在对象完全初始化之后。

java 复制代码
private static volatile Singleton instance;

结论

DCL 导致的可见性问题本质是指令重排序 使未完全初始化的对象对其他线程可见。通过 volatile 的内存屏障 来禁止这种重排序即可修复。在现代 Java 中,更推荐使用静态内部类 或枚举方式实现单例,它们更简洁且天然避免此类问题。

相关推荐
郝学胜-神的一滴3 小时前
Web安全入门:读懂Web世界里的攻防
前端·网络·后端·安全·web安全·hibernate
天若有情6733 小时前
从零搭建校园二手交易系统:Vue2 + SpringBoot 4.x 全栈实战
java·spring boot·后端
Lost of 程序猿4 小时前
责任链模式实战:审批流里“层层把关“的艺术
后端·设计模式·asp.net·责任链模式
知守观4 小时前
Maven SNAPSHOT 更新排查:-U 无效、clean 无用,问题在 _remote.repositories(附完整链路)
java·后端·maven
IT_陈寒4 小时前
一文搞懂SpringBoot的核心概念
前端·人工智能·后端
Mikko74 小时前
fastjson 1.2.84 能在 JDK 7 上跑吗?实测:编译能过,一启动就 UnsupportedClassVersionError
java·后端·jdk·fastjson·安全漏洞
高频因子挖掘机4 小时前
股票 API 返回 HTTP 429 怎么办?Python 量化系统的限流排查与重试设计
后端·github·api
mldong4 小时前
为了 token 自由,我把 jeeflow 工作流引擎用 MoonBit 写了一遍
后端·架构
高频因子挖掘机5 小时前
从回测到实盘:量化策略为什么不应该直接依赖行情 API
后端·github·api