导读:本期聚焦于小伙伴创作的《C#中的双重检查锁定为什么在没有volatile时是错的》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#中的双重检查锁定为什么在没有volatile时是错的》有用,将其分享出去将是对创作者最好的鼓励。

在C#多线程场景下实现单例模式时,双重检查锁定是很多开发者会选择的写法,它试图在减少锁开销的同时保证单例的唯一性,但这种写法如果缺少volatile关键字修饰,会出现不符合预期的问题。

C#中的双重检查锁定为什么在没有volatile时是错的

常见的双重检查锁定实现

先来看一段典型的没有使用volatile的双重检查锁定单例代码:

public class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();

    private Singleton() { }

    public static Singleton GetInstance()
    {
        if (_instance == null) // 第一次检查
        {
            lock (_lock)
            {
                if (_instance == null) // 第二次检查
                {
                    _instance = new Singleton(); // 创建实例
                }
            }
        }
        return _instance;
    }
}

这段代码的初衷是:当_instance已经初始化后,后续线程不需要进入锁逻辑,直接返回实例,减少锁竞争带来的性能损耗。但这段代码在多线程环境下并不安全。

问题产生的核心原因

指令重排序问题

C#中创建对象的过程并不是原子操作,_instance = new Singleton()这行代码大致会被拆分成三个步骤:

  • 分配Singleton对象所需的内存空间
  • 调用Singleton的构造函数,初始化对象成员
  • 将_instance引用指向分配好的内存空间

在没有volatile修饰的情况下,编译器和CPU可能会对这三个步骤进行指令重排序,步骤二和步骤三的执行顺序可能被调换。也就是说,可能出现_instance已经指向了内存空间,但构造函数还没有执行完成的情况。

线程安全漏洞场景

假设现在有两个线程A和B同时调用GetInstance方法:

  1. 线程A进入方法,第一次检查发现_instance为null,于是进入lock逻辑
  2. 线程A执行_instance = new Singleton(),发生了指令重排序,先执行了步骤三,_instance已经指向了内存,但构造函数还没执行
  3. 此时线程B进入方法,第一次检查_instance不为null,直接返回_instance
  4. 线程B拿到的_instance是还没有完成初始化的对象,使用这个对象就会出现不可预期的错误

缺少内存屏障

volatile关键字的作用除了禁止指令重排序,还会保证变量的读写操作都有内存屏障。没有volatile修饰时,一个线程对_instance的写入操作,对其他线程的可见性无法保证,其他线程可能一直读取到旧的null值,或者读取到未初始化完成的引用。

正确的实现方式

要给_instance加上volatile修饰,禁止指令重排序,保证可见性:

public class Singleton
{
    private static volatile Singleton _instance;
    private static readonly object _lock = new object();

    private Singleton() { }

    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }
}

除了这种方式,也可以使用静态构造函数或者Lazy<T>来实现线程安全的单例,这些方式不需要自己处理双重检查锁定的细节,也能保证线程安全,是更推荐的实现方案。

总结

双重检查锁定在没有volatile修饰时,会因为指令重排序和内存可见性问题导致线程安全漏洞,实际开发中如果要使用这种写法,一定要给单例引用加上volatile关键字,或者直接使用C#提供的Lazy<T>等更可靠的方式实现单例,避免踩坑。

C#双重检查锁定Double_Checked_Lockingvolatile线程安全修改时间:2026-07-05 13:51:21

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。