编辑
2025-09-20
C#
00

目录

什么是单例模式?
何时使用单例模式?
单例模式的基本结构
核心要素
基本实现示例
线程安全的单例实现
使用锁实现线程安全
双重检查锁定优化性能
最佳实践:使用Lazy<T>实现延迟初始化
饿汉式vs懒汉式加载
饿汉式实现示例
单例模式的缺点
总结

在C#编程中,单例模式(Singleton Pattern)是一种极其实用且常见的设计模式。它确保一个类只有一个实例,并提供一个全局访问点。本文将深入剖析单例模式的原理、实现方式和实际应用场景,帮助你全面掌握这一重要的设计模式。

什么是单例模式?

单例模式是一种创建型设计模式,它确保一个类仅有一个实例,并提供一个全局访问点。简单来说,如果你需要一个类在整个应用程序中只存在一个对象(比如日志管理器、配置处理器或共享资源),单例模式正是你所需要的。

何时使用单例模式?

在以下情况下,单例模式是理想的选择:

  • 需要限制类只能有一个实例
  • 需要控制对共享资源的访问,如文件处理器、数据库连接、日志服务等
  • 需要一个在整个应用程序中被重用的集中式对象

单例模式的基本结构

核心要素

  • 私有构造函数:防止外部直接实例化
  • 静态变量:持有唯一的实例
  • 公共静态方法或属性:提供访问实例的入口

基本实现示例

C#
namespace AppSingleton { public class Singleton { private static Singleton instance = null; private Singleton() { Console.WriteLine("单例实例已创建"); } public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } public void SomeBusinessLogic() { Console.WriteLine("执行业务逻辑"); } } internal class Program { static void Main(string[] args) { Singleton s1 = Singleton.GetInstance(); Singleton s2 = Singleton.GetInstance(); // 验证两个引用指向同一对象 Console.WriteLine("s1和s2是否是同一实例: " + (s1 == s2)); s1.SomeBusinessLogic(); Console.ReadKey(); } } }

image.png

上面的基本实现在多线程环境中可能会出问题。如果两个线程同时检查instance == null并尝试创建实例,可能会导致创建多个实例。

线程安全的单例实现

使用锁实现线程安全

C#
namespace AppSingleton { public class ThreadSafeSingleton { private static ThreadSafeSingleton instance = null; private static readonly object lockObj = new object(); private ThreadSafeSingleton() { Console.WriteLine("线程安全的单例实例已创建"); } public static ThreadSafeSingleton GetInstance() { lock (lockObj) // 确保线程安全 { if (instance == null) { instance = new ThreadSafeSingleton(); } } return instance; } } internal class Program { static void Main(string[] args) { ThreadSafeSingleton s1 = ThreadSafeSingleton.GetInstance(); ThreadSafeSingleton s2 = ThreadSafeSingleton.GetInstance(); // 验证两个引用指向同一对象 Console.WriteLine("s1和s2是否是同一实例: " + (s1 == s2)); Console.ReadKey(); } } }

image.png

双重检查锁定优化性能

C#
using System.Collections.Concurrent; namespace AppSingleton { public sealed class DoubleCheckSingleton { private static DoubleCheckSingleton instance = null; private static readonly object lockObj = new object(); private DoubleCheckSingleton() { } public static DoubleCheckSingleton GetInstance() { // 第一次检查 if (instance == null) { // 加锁 lock (lockObj) { // 第二次检查 if (instance == null) { instance = new DoubleCheckSingleton(); } } } return instance; } } internal class Program { static void Main(string[] args) { const int threadCount = 5; var instances = new ConcurrentBag<DoubleCheckSingleton>(); var tasks = new List<Task>(); // 创建多个任务同时获取单例 for (int i = 0; i < threadCount; i++) { int taskId = i; tasks.Add(Task.Run(() => { Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 任务{taskId}开始 - 线程ID: {Thread.CurrentThread.ManagedThreadId}"); // 添加随机延迟,增加并发冲突的可能性 Thread.Sleep(new Random().Next(10, 50)); var instance = DoubleCheckSingleton.GetInstance(); instances.Add(instance); Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 任务{taskId}完成,获得实例哈希码: {instance.GetHashCode()} - 线程ID: {Thread.CurrentThread.ManagedThreadId}"); })); } // 等待所有任务完成 Task.WaitAll(tasks.ToArray()); // 验证所有实例是否相同 var instanceArray = instances.ToArray(); Console.WriteLine($"\n并发测试结果:"); Console.WriteLine($"总共获得 {instanceArray.Length} 个实例"); } } }

image.png

最佳实践:使用Lazy实现延迟初始化

C#提供了Lazy<T>类,可以轻松实现线程安全的延迟初始化:

C#
using System.Collections.Concurrent; namespace AppSingleton { public class LazySingleton { private static readonly Lazy<LazySingleton> instance = new Lazy<LazySingleton>(() => new LazySingleton()); private LazySingleton() { Console.WriteLine("Lazy单例实例已创建"); } public static LazySingleton Instance => instance.Value; public void DoWork() { Console.WriteLine("Lazy单例执行工作..."); } } internal class Program { static void Main(string[] args) { Console.WriteLine("程序开始,单例尚未创建"); // 首次访问时创建实例 LazySingleton singleton = LazySingleton.Instance; singleton.DoWork(); // 再次访问不会创建新实例 LazySingleton anotherReference = LazySingleton.Instance; Console.WriteLine("是否是同一实例: " + (singleton == anotherReference)); } } }

image.png

饿汉式vs懒汉式加载

方式描述适用场景
饿汉式实例在应用程序启动时创建性能关键场景,初始化开销较小
懒汉式实例在首次需要时创建节省内存和启动时间,初始化开销大

饿汉式实现示例

C#
using System.Collections.Concurrent; namespace AppSingleton { public sealed class EagerSingleton { private static readonly EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { Console.WriteLine("饿汉式单例在程序启动时已创建"); } // 公共属性 public static EagerSingleton Instance { get { return instance; } } } internal class Program { static void Main(string[] args) { var s1 = EagerSingleton.Instance; var s2 = EagerSingleton.Instance; var s3 = EagerSingleton.Instance; Console.WriteLine($"s1 哈希码: {s1.GetHashCode()}"); Console.WriteLine($"s2 哈希码: {s2.GetHashCode()}"); Console.WriteLine($"s3 哈希码: {s3.GetHashCode()}"); Console.WriteLine($"s1 == s2: {s1 == s2}"); Console.WriteLine($"s1 == s3: {s1 == s3}"); Console.WriteLine($"ReferenceEquals(s1, s2): {ReferenceEquals(s1, s2)}"); Console.ReadKey(); } } }

image.png

单例模式的缺点

使用单例模式时应该注意以下几点:

  • 可能使单元测试变得困难(紧耦合)
  • 有时会被过度使用,当简单的静态类就足够时,这个估计用的最多了。
  • 存在应用程序中隐藏依赖关系的风险
  • 违反单一职责原则,同时负责自身的实例化和业务逻辑

总结

单例模式是C#中一种强大且常用的设计模式,它确保类只有一个实例并提供全局访问点。使用单例模式时要注意以下几点:

  • 确保线程安全,特别是在多线程应用中
  • 考虑使用Lazy<T>实现延迟加载,提高性能
  • 根据应用场景选择合适的实现方式(饿汉式或懒汉式)
  • 避免过度使用,只在确实需要单一实例时使用
  • 谨慎处理依赖关系,防止隐藏依赖

单例模式在日志记录、配置管理、数据库连接池和缓存系统等场景中特别有用。通过正确实现和应用单例模式,你可以有效控制资源使用,优化应用性能,并简化全局状态管理。

希望本文对你理解和应用C#单例模式有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。


关键词: C#单例模式, 设计模式, 线程安全单例, Lazy初始化, 饿汉式, 懒汉式, 日志记录器, 配置管理, 数据库连接, 缓存机制, C#编程技巧

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!