C#——单例模式

代码:

复制代码
public class Singleton
{
    private static Singleton _instance;
    private static readonly object locker = new object();

    private Singleton() { }


    public static Singleton GetInstance()
    {
        // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
        // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
        // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
        //双重锁定
        if (_instance == null)
        {
            lock (locker)
            {
                // 如果类的实例不存在则创建,否则直接返回
                if (_instance == null)
                {
                    _instance = new Singleton();
                }

            }
        }


        return _instance;
    }



    public void DoBusiness()
    {
        Console.WriteLine("单例对象执行业务逻辑");
    }

    public void ShowInfo(string message)
    {
        Console.WriteLine($"单例对象接收消息:{message}");
    }

}

调用:

复制代码
    internal class SingleTest
    {
        public void DemoMethod()
        {

            Singleton sc = Singleton.GetInstance();

            sc.DoBusiness();
        }


        // 多线程调用示例
        public void MultiThreadDemo()
        {
            // 线程1
            Task.Run(() =>
            {
                Singleton sc = Singleton.GetInstance();
                sc.ShowInfo("线程1的消息");
            });

            // 线程2
            Task.Run(() =>
            {
                Singleton sc = Singleton.GetInstance();
                sc.ShowInfo("线程2的消息");
            });
        }

        //运行后会发现,即使两个线程同时调用,也只会创建一个单例实例。


    }

解释:

复制代码
// 1. 私有静态变量:用于存储Singleton的唯一实例,初始值为null
private static Singleton _instance;

// 2. 私有静态只读锁对象:用于多线程环境下的同步锁,保证线程安全
private static readonly object locker = new object();
  • _instance :这是单例的 "容器",整个程序中只有一个这个静态变量,所以能存储唯一的实例。初始值为null,表示实例还未创建。
  • locker :这是一个 "锁标记",用于多线程时的互斥访问。readonly修饰保证这个锁对象不会被篡改,是线程同步的关键。
复制代码
private Singleton() { }

private Singleton() { }能禁止外部用new创建实例,核心原因是C# 的访问修饰符规则限制了private构造函数的调用范围 ------ 它只能在类的内部被访问,外部代码完全没有权限调用这个构造函数。

当用new 类名()创建实例时,C# 编译器会自动调用这个类的构造函数来完成对象的初始化。构造函数是创建实例的 "必经之路",没有调用构造函数,就无法创建类的实例。

复制代码
public static Singleton GetInstance()
{
    // 第一次检查:快速判断实例是否已存在(无锁,提升性能)
    if (_instance == null)
    {
        // 加锁:保证同一时间只有一个线程能进入这个代码块
        lock (locker)
        {
            // 第二次检查:在锁内再次判断实例是否已存在(防止多线程重复创建)
            if (_instance == null)
            {
                // 创建唯一的实例
                _instance = new Singleton();
            }
        }
    }
    // 返回唯一实例
    return _instance;
}

这部分是整个单例模式的核心,也是 "双重检查锁定" 的关键,我们拆成三步理解:

(1)第一次检查(外层if (_instance == null)):提升性能
  • 如果实例已经创建(_instance != null),直接返回实例,不需要加锁。这是性能优化的关键 ------ 因为大部分情况下实例已经存在,加锁是耗时操作,这一步能避免不必要的锁开销。
  • 只有实例还未创建时(_instance == null),才会进入加锁逻辑。
(2)lock (locker):保证线程互斥
  • 当多个线程同时走到这一步时,lock语句会让第一个线程获取locker的锁,其他线程会被挂起等待,直到第一个线程释放锁。
  • 这就保证了同一时间只有一个线程能进入锁内的代码块,避免了多线程同时创建实例的问题。
(3)第二次检查(内层if (_instance == null)):防止重复创建
  • 这是最容易被忽略但最关键的一步!举个例子:
    • 线程 A 和线程 B 同时走到外层if,发现_instance == null;
    • 线程 A 先获取锁,进入锁内,此时内层if发现_instance == null,创建实例;
    • 线程 A 释放锁后,线程 B 获取锁,进入锁内 ------ 如果没有内层if,线程 B 会再次创建实例,破坏单例;
    • 有了内层if,线程 B 会发现_instance已经不为null,直接跳过创建步骤,保证实例唯一。
相关推荐
波力海苔夹心脆6756 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
不会算法的笨小孩6 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
AI导出鸭PC端8 小时前
Perplexity批量导出没有PDF模式?AI导出鸭插件补上最后一块拼图
人工智能·pdf·c#·ai导出鸭
Hhy_11078 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
starzy19908 小时前
深入理解Scala函数定义:从方法到函数的类型跨越
开发语言·scala
starzy19909 小时前
深入理解Scala递归函数:从栈溢出到尾调用优化
开发语言·scala
三*一9 小时前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
YSL0701249 小时前
C++基础
开发语言·c++
若亦_9 小时前
Java中关于this的三种用法
java·开发语言·intellij-idea
Risehuxyc9 小时前
C# 选用更流行的语言版本
c#