23种设计模式
单例模式
本文档使用 MrDoc 发布
-
+
首页
单例模式
# 介绍 单例模式是软件⼯程学中最富盛名的设计模式之⼀。从本质上看,单例模式只允许被其⾃⾝实例化⼀次,且向外部提供了⼀个访问该实例的接⼝。通常来说,单例对象进⾏实例化时⼀般不带参数,因为如果不同的实例化请求传递的参数不同的话会导致问题的产⽣。(若多个请求都是传递的同样的参数的话,⼯⼚模式更应该被考虑)。 C#中实现单例有很多种⽅法,本⽂将按顺序介绍⾮线程安全、完全懒汉式、线程安全和低/⾼性能集中版本。 在所有的实现版本中,都有以下⼏个共同点: - 唯⼀的、私有的且⽆参的构造函数,这样不允许外部类进⾏实例化; - 类是密封的,尽管这不是强制的,但是严格来讲从上⼀点来看密封类能有助于JIT的优化; - ⼀个静态变量应该指向类的唯⼀实例; - ⼀个公共的静态变量⽤于获得这个类的唯⼀实例(如果需要,应该创建它); 需要注意的是,本⽂中所有的例⼦中都是⽤⼀个 public static Instance的变量来访问单例类实例,要将其转换成公共函数是很容易的,但是这样并不会带来效率和线程安全上的提升。 # 版本1:非线程安全 ```C# public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if (instance == null) instance = new Singleton(); return instance; } } } ``` 该版本在多线程下是不安全的,会创建多个实例,请不要在⽣产环境中使⽤! 因为如果两个线程同时运⾏到if(instance==null)判断时,就会创建两个实例,这是违背单例模式的初衷的。实际上在后⾯那个线程进⾏判断是已经⽣成了⼀个实例,但是对于不同的线程来说除⾮进⾏了线程间的通信,否则它是不知道的。 # 版本2:简单的线程安全 ```C# public sealed class Singleton2 { private static Singleton2 instance = null; private static readonly object obj = new object(); private Singleton2() { } public Singleton2 Instance { get { lock (obj) { if (instance == null) { instance = new Singleton2(); } return instance; } } } } ``` 该版本是线程安全的。通过对⼀个过线程共享的对象进⾏加锁操作,保证了在同⼀时刻只有⼀个线程在执⾏lock{}⾥的代码。当第⼀个线程在进⾏instance判断或创建时,后续线程必须等待直到前⼀线程执⾏完毕,因此保证了只有第⼀个线程能够创建instance实例。 但不幸的是,因为每次对instance的请求都会进⾏lock操作,其性能是不佳的。 需要注意的是,这⾥使⽤了⼀个private static object变量进⾏锁定,这是因为当如果对⼀个外部类可以访问的对象进⾏锁定时会导致性能低下甚⾄死锁。因此通常来说为了保证线程安全,进⾏加锁的对象应该是private的。 # 版本3:Double-check locking的线程安全 ```C# public sealed class Singleton3 { private static Singleton3 instance = null; private static object obj = new object(); private Singleton3() { } public static Singleton3 Instance { get { if (instance == null) { lock (obj) { if (instance == null) { instance = new Singleton3(); } } } return instance; } } } ``` 该版本中试图去避免每次访问都进⾏加锁操作并实现线程安全。然后,这段代码对Java不起作⽤,因Java的内存模型不能保证在构造函数⼀定在其他对象引⽤instance之前完成。还有重要的⼀点,它不如后⾯的实现⽅式。 # 版本4:不完全懒汉式,但不加锁的线程安全 ```C# public sealed class Singleton4 { private static readonly Singleton4 instance = new Singleton4(); /// <summary> /// 显式的静态构造函数⽤来告诉C#编译器在其内容实例化之前不要标记其类型 /// </summary> static Singleton4() { } private Singleton4() { } public static Singleton4 Instance { get { return instance; } } } ``` 这个版本是的实现⾮常的简单,但是却⼜是线程安全的。C#的静态构造函数只有在当其类的实例被创建或者有静态成员被引⽤时执⾏,在整个应⽤程序域中只会被执⾏⼀次。使⽤当前⽅式明显⽐前⾯版本中进⾏额外的判断要快。 当然这个版本也存在⼀些瑕疵: - 不是真正意义上的懒汉模式(需要的时候才创建实例),若单例类还存在其他静态成员,当其他类第⼀次引⽤这些成员时便会创建该instance。下个版本实现会修正这个问题; - 只有.NET中才具有beforefieldinit特性,即懒汉式实现。且在.Net 1.1以前的编译器不⽀持,不过这个现在来看问题不⼤; 所有版本中,只有这⾥将instance设置成了readonly,这不仅保证了代码的⾼校且显得⼗分短⼩。 # 版本5:完全懒汉实例化 ```C# public sealed class Singleton5 { private Singleton5() { } public static Singleton5 Instance { get { return Nested.instance; } } private class Nested { static Nested() { } internal static readonly Singleton5 instance = new Singleton5(); } } ``` 该版本看起来稍微复杂难懂,其实只是在写法上实现了上⼀版本的瑕疵,通过内嵌类的⽅式先实现了只有在真正应⽤Instance时才进⾏实例化。其性能表现与上⼀版本⽆异。 # 版本6:使⽤.NET 4 Lazy type 特性 ```C# public sealed class Singleton6 { private static readonly Lazy<Singleton6> lazy = new Lazy<Singleton6>(() => new Singleton6()); public static Singleton6 Instance { get { return lazy.Value; } } private Singleton6() { } } ``` 如果你使⽤的是.NET 4或其以上版本,可以使⽤System.Lazy type来实现完全懒汉式。其代码看起来也很简洁且性能表现也很好。 # 总结 上述提供的⼏种实现⽅法中,⼀般情况下提倡使⽤Version 4,除⾮遇到有时早于单列类实例化时就引⽤了其他静态成员。这种情况下,Version2⼀旦被考虑,虽然它看起来会因加锁耗时,但是其实运⾏起来并没有你想的那么慢,关键是你很容易写对它。显然Version 1你永远都不应该 考虑,Version 3在与Version 5的对⽐下也是不在考虑范围之内的。 # Counter类 ```C# public sealed class Counter { public DateTime CreateTime { get; private set; } public Counter() { CreateTime = DateTime.Now; Console.WriteLine($"实例化时间为:{CreateTime}"); } // 方式一:使用静态实例实现单例,适用于单线程 private static Counter singleton = null; public static Counter GetSingleton() { if (singleton == null) singleton = new Counter(); return singleton; } // 方式二:使用静态实例实现单例,适用于多线程 public static Counter Instance = new Counter(); private static readonly object locker = new object(); // 方式三:加锁,能用,性能差 public static Counter Singleton { get { lock (locker) { if (singleton == null) singleton = new Counter(); return singleton; } } } // 方式四:双判断锁,加强性能 public static Counter Singleton2 { get { if (singleton == null) { lock (locker) { if (singleton == null) singleton = new Counter(); } } return singleton; } } // 方式五:Lazy 懒加载 private static readonly Lazy<Counter> lazyInstance = new Lazy<Counter>(() => new Counter()); public static Counter LazyInstance => lazyInstance.Value; } ``` # 测试 ```C# static void Main(string[] args) { // 普通方式创建对象 /* Counter c1 = new Counter(); Counter c2 = new Counter(); Console.WriteLine(ReferenceEquals(c1, c2)); Console.ReadKey(); */ // 方式一:单线程 //Counter c1 = Counter.GetSingleton(); //Counter c2 = Counter.GetSingleton(); //Console.WriteLine(ReferenceEquals(c1, c2)); //Console.ReadKey(); // 方式一:多线程(有问题) /* for (int i=0; i<10;i++) { Task.Run(() => { Counter c3 = Counter.GetSingleton(); }); } Console.ReadKey(); */ // 方式二:多线程 /* for (int i=0; i<10;i++) { Task.Run(() => { Counter c3 = Counter.Instance; }); } Console.ReadKey(); */ // 方式三:多线程(有2次,是因为方式二的静态实例对象) /* for (int i=0; i<10;i++) { Task.Run(() => { Counter c3 = Counter.Singleton; }); } Console.ReadKey(); */ // 方式四:多线程 /* for (int i = 0; i < 10; i++) { Task.Run(() => { Counter c3 = Counter.Singleton2; }); } Console.ReadKey(); */ // 方式五:多线程 for (int i = 0; i < 10; i++) { Task.Run(() => { Counter c3 = Counter.LazyInstance; }); } Console.ReadKey(); } ```
张泽楠
2025年4月21日 10:33
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码