当前位置: 首页 > news >正文

C# 设计模式——单例模式

在C#中,单例设计模式(Singleton Pattern) 是一种创建型设计模式,核心目标是确保一个类在整个应用程序生命周期中只存在一个实例,并提供一个全局访问点供外部使用。这种模式适合管理共享资源(如配置文件、日志记录器、数据库连接池等),避免频繁创建实例导致的资源浪费或状态不一致。

单例模式的核心要素

要实现单例模式,必须满足以下3个条件:

  1. 私有构造函数:阻止外部通过new关键字创建实例。
  2. 静态私有实例:在类内部维护唯一的实例对象。
  3. 静态公共访问点:提供一个全局方法/属性,返回该唯一实例。

一、常见实现方式(从简单到线程安全)

单例模式的实现难点在于多线程环境下的线程安全(避免并发创建多个实例)和延迟初始化(按需创建实例,节省资源)。以下是几种典型实现:

1. 饿汉式(Eager Initialization)

特点:类加载时立即初始化实例(非延迟加载),天然线程安全(C#中静态构造函数由CLR保证线程安全)。

public sealed class SingletonEager
{// 1. 静态私有实例:类加载时直接初始化private static readonly SingletonEager _instance = new SingletonEager();// 2. 私有构造函数:阻止外部实例化private SingletonEager() { }// 3. 静态公共访问点:返回唯一实例public static SingletonEager Instance => _instance;
}

优缺点

  • 优点:实现简单,线程安全(无需额外处理)。
  • 缺点:无论是否使用,实例都会在类加载时创建(若实例初始化耗时,会影响程序启动速度)。
2. 懒汉式(Lazy Initialization,非线程安全)

特点:延迟初始化(第一次使用时才创建实例),但多线程环境下可能创建多个实例(非线程安全)。

public sealed class SingletonLazyUnsafe
{// 1. 静态私有实例:初始为null(延迟初始化)private static SingletonLazyUnsafe _instance;// 2. 私有构造函数private SingletonLazyUnsafe() { }// 3. 静态访问点:第一次调用时创建实例public static SingletonLazyUnsafe Instance{get{if (_instance == null){_instance = new SingletonLazyUnsafe(); // 多线程并发时可能多次执行}return _instance;}}
}

问题:多线程同时调用Instance时,若_instancenull,多个线程会同时进入if语句,创建多个实例,违反单例原则。
适用场景:仅适用于单线程环境(如简单工具类)。

3. 线程安全的懒汉式(双重检查锁定,Double-Check Locking)

特点:结合延迟初始化和线程安全,通过lock加锁和双重判断避免并发问题,是实际开发中最常用的实现之一。

public sealed class SingletonDoubleCheck
{// 1. 静态私有实例:用volatile修饰,防止指令重排序(关键!)private static volatile SingletonDoubleCheck _instance;// 2. 锁对象:用于线程同步private static readonly object _lock = new object();// 3. 私有构造函数private SingletonDoubleCheck() { }// 4. 静态访问点:双重检查+锁定public static SingletonDoubleCheck Instance{get{// 第一次检查:若实例已存在,直接返回(避免每次加锁,提高性能)if (_instance == null){// 加锁:确保同一时间只有一个线程进入初始化逻辑lock (_lock){// 第二次检查:防止多个线程等待锁时,已有线程创建了实例if (_instance == null){_instance = new SingletonDoubleCheck();}}}return _instance;}}
}

关键点解析

  • volatile关键字:防止编译器对_instance = new SingletonDoubleCheck()进行指令重排序(该操作可分解为“分配内存→初始化对象→赋值给变量”,若重排序可能导致其他线程获取到未初始化的实例)。
  • 双重检查:第一次检查避免不必要的加锁(提高性能),第二次检查防止多线程等待锁时重复创建实例。

优缺点

  • 优点:延迟初始化、线程安全、性能优异(仅首次创建时加锁)。
  • 缺点:实现稍复杂,需注意volatile和双重检查的细节。
4. 静态内部类(Lazy Initialization with Static Nested Class)

特点:利用C#静态内部类的特性实现延迟初始化和线程安全(推荐使用,简洁且无锁)。

public sealed class SingletonNested
{// 1. 私有构造函数private SingletonNested() { }// 2. 静态内部类:仅在被调用时加载private static class Nested{// 静态内部类的静态字段:由CLR保证线程安全,且仅在第一次访问时初始化internal static readonly SingletonNested Instance = new SingletonNested();}// 3. 公共访问点:调用内部类的实例public static SingletonNested Instance => Nested.Instance;
}

原理

  • 外部类SingletonNested加载时,内部类Nested不会被加载。
  • 第一次调用Instance时,内部类Nested被加载,其静态字段Instance被初始化(CLR保证静态字段初始化是线程安全的)。

优缺点

  • 优点:延迟初始化、线程安全、实现简洁(无需手动加锁)、性能好。
  • 缺点:无法传递参数给单例的构造函数(若单例需要初始化参数,此方式不适用)。
5. 使用Lazy<T>(.NET 4.0+ 推荐方式)

特点:利用.NET内置的Lazy<T>类实现延迟初始化,自带线程安全机制,无需手动处理锁逻辑。

public sealed class SingletonLazy
{// 1. 用Lazy<T>包装实例:指定初始化方法,默认线程安全private static readonly Lazy<SingletonLazy> _lazyInstance = new Lazy<SingletonLazy>(() => new SingletonLazy());// 2. 私有构造函数private SingletonLazy() { }// 3. 公共访问点:通过Lazy<T>.Value获取实例public static SingletonLazy Instance => _lazyInstance.Value;
}

Lazy<T>的线程安全

  • 默认情况下,Lazy<T>使用LazyThreadSafetyMode.ExecutionAndPublication模式,确保多线程环境下只初始化一次。
  • 若需自定义线程安全策略,可通过Lazy<T>的构造函数参数指定(如LazyThreadSafetyMode.None关闭线程安全,适合单线程)。

优缺点

  • 优点:延迟初始化、线程安全(内置处理)、代码简洁、支持传递参数(通过Lazy<T>的初始化委托)。
  • 缺点:依赖.NET 4.0及以上框架(现代项目基本都满足)。

二、单例模式的注意事项

  1. 防止反射攻击
    私有构造函数可能被反射强行调用(如typeof(Singleton).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance).Invoke(null)),导致创建多个实例。
    防护方式:在构造函数中检查实例是否已存在,若存在则抛异常:

    private SingletonLazy()
    {if (_lazyInstance.IsValueCreated){throw new InvalidOperationException("单例实例已存在,禁止重复创建");}
    }
    
  2. 序列化问题
    若单例类需要序列化(实现ISerializable),反序列化时可能创建新实例。需重写GetObjectData和反序列化构造函数,确保返回原实例:

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {// 序列化时不存储数据,反序列化时返回单例
    }private SingletonLazy(SerializationInfo info, StreamingContext context)
    {// 反序列化时强制返回现有实例if (_lazyInstance.IsValueCreated){throw new InvalidOperationException("禁止反序列化创建单例");}
    }
    
  3. 单例的生命周期
    单例实例通常随应用程序生命周期存在(除非手动释放),适合管理全局资源。但过度使用会导致代码耦合度升高(依赖全局状态),不利于单元测试。

三、适用场景

  • 全局配置管理(如AppConfig):确保配置只加载一次,全局共享。
  • 日志记录器(如Logger):避免多个日志实例导致的文件锁冲突。
  • 数据库连接池:统一管理连接,防止连接数爆炸。
  • 缓存管理器:全局缓存实例,保证缓存数据一致性。

总结

单例模式的核心是“唯一实例 + 全局访问”,在C#中推荐使用以下两种实现:

  • 简单场景(无参数、需简洁):静态内部类或**Lazy<T>**。
  • 复杂场景(需线程安全、可能传递参数):双重检查锁定或**Lazy<T>**(Lazy<T>更推荐,内置线程安全)。

使用时需注意线程安全、反射攻击和序列化问题,避免滥用单例导致代码灵活性下降。

http://www.dtcms.com/a/507117.html

相关文章:

  • 单例模式与线程池的实际应用
  • Ubuntu24 逻辑卷磁盘扩容全流程
  • 网站加载速度慢的原因佛山网站建设公司价格
  • 容器化与调度:使用 Docker 与 K8s 管理分布式淘宝商品数据采集任务
  • 微服务熔断降级方案对比:Hystrix、Resilience4j与Sentinel实践
  • 解决在windows中基于Spring AI 集成文件管理MCP服务遇到的问题
  • 【研究生随笔】PyTorch中的概率论
  • 青少年活动中心网站建设依据青岛标志设计公司
  • 网站三要素关键词 描述怎么做网站建设报价单 excel
  • Kubernetes Pod 管理全攻略:从基础操作到进阶优化
  • 基于 OpenHarmony 6.0 的智能充电桩技术方案与实现
  • 三步破局:一致性轨迹强化学习开启扩散语言模型“又快又好”推理新时代
  • Node.js | pnpm下载安装与环境配置
  • 递归-二叉树中的深搜-2331.计算布尔二叉树的值-力扣(LeetCode)
  • 下部刚刚是上部
  • 自动化产线效率低,主要看这四个环节
  • 如何查询网站开发语言杭州企业网站制作
  • sql server网站建设电子商务网络营销的概念
  • 网页制作基础教程代码网站seo软件
  • kafka中server.properties中的关键配置
  • 帧率、分辨率、码率
  • Linux补充01:HTTPS协议原理
  • 2025全球风电盛会CWP今日开展
  • Linux网络 网络层
  • 一个专门做各种恐怖片的电影网站怎样用记事本做网站
  • 织梦网站后台密码wordpress forandroid
  • STP的配置
  • 解锁细胞青春密码:美国 WJCZ 麦角硫因时光胶囊,用前沿生物科技对抗肌肤衰老
  • CTFSHOW—WEB4
  • MySQL InnoDB 状态(SHOW ENGINE INNODB STATUS)深度分析与性能优化建议