模式是一个非常有趣的话题,它是对特定前提下重复出现问题的一个普遍解答,它是一种思想,使用得当也会对设计、实施提供帮助。
简单的说,软件开发发展了几十年,前人遇到了很多很多的问题,有些人做了归纳总结,把某一类问题总结出一个解决套路,这些套路可以有效的解决类似的问题。形成了我们的23种模式。
概述
Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点[DP]。
单例模式(Singleton)结构图
多线程时的单例
Lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它一直将等待(即被阻止),直到该对象被释放。[MSDN]
五种实现
(一)简单实现
- 简单实现很容易理解,只有在实例为空的情况下才创建新的实例。
- 但若处在多线程情况下,会出现问题:
- 线程一执行完if(instance==null)这句后,准备进入下一句创建实例时,被挂起;
- 此时切换到线程二,线程二全部执行完毕后,实例已经创建完毕。
- 线程一激活,继续创建实例,造成双实例情况。违背单间原则。
代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SingletonPattern { /// <summary> /// <para>单例模式:简单实现</para> /// </summary> public sealed class Singleton { //静态构造函数 static Singleton() { } //私有变量 private static Singleton instance = null; //共有属性 public static Singleton Instance { get { if (null == instance) { instance = new Singleton(); } return instance; } } } }
安全的线程
此设计更正了简单实现出现的情况,其不会出现双线程时创建多个实例。
但是,进入Instance的Get方法后,直接被锁住,直到return结束后,才会允许其他线程进入。此处大大的降低了效率,因为其不管Instance是否为空,都加锁,有点不合适。
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace SingletonPattern 8 { 9 /// <summary> 10 /// <para>单例模式:安全线程</para> 11 /// </summary> 12 public sealed class SecurityThreadSingleton 13 { 14 //静态构造函数 15 static SecurityThreadSingleton() { } 16 17 //用户加锁 18 static readonly object padlock = new object(); 19 20 //私有变量 21 private static SecurityThreadSingleton instance = null; 22 23 //共有属性 24 public static SecurityThreadSingleton Instance 25 { 26 get 27 { 28 //锁定 29 lock (padlock) 30 { 31 if (null == instance) 32 { 33 instance = new SecurityThreadSingleton(); 34 } 35 } 36 return instance; 37 } 38 } 39 } 40 }
双重锁定
- 双重锁定改善了安全的线程出现的性能降低的问题。如若Instance不为空,则不需要进入锁,提高了效率。
- 有人会问为什么锁外面判断一次实例是否为空,锁里面还有一次判断?
主要是考虑多线程,一个线程判断实例为空,进入锁时被挂起,切换到线程二,线程二恰好创建了实例,线程一继续执行时加上这层判断非常必要。
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace SingletonPattern 8 { 9 /// <summary> 10 /// <para>单例模式:双重锁定</para> 11 /// </summary> 12 public class DoubleLockSingleton 13 { 14 //静态构造函数 15 static DoubleLockSingleton() { } 16 17 //用户加锁 18 private static readonly object padlock = new object(); 19 20 //私有变量 21 private static DoubleLockSingleton instance = null; 22 23 //共有属性 24 public static DoubleLockSingleton Instance 25 { 26 get 27 { 28 if (null == instance) 29 { 30 //锁定 31 lock (padlock) 32 { 33 if (null == instance) 34 { 35 instance = new DoubleLockSingleton(); 36 } 37 } 38 } 39 return instance; 40 } 41 } 42 } 43 }
静态初始化
静态只读字段instance会在类被访问的时候被实例化,所以这点就与按需创建的原则相违背。
不过如若当前你的机器性能非常好,不在乎一个引用实例占用内存的情况,这点也就不用在乎。但是如若当前机器性能一般,需要计算内存的使用,建议使用下一种方法。
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace SingletonPattern 8 { 9 /// <summary> 10 /// <para>单例模式:静态初始化</para> 11 /// </summary> 12 public sealed class StaticInitSingleton 13 { 14 //静态构造函数 15 static StaticInitSingleton() { } 16 17 //私有构造函数 18 private StaticInitSingleton() { } 19 20 //私有静态只读属性 21 private static readonly StaticInitSingleton instance = new StaticInitSingleton(); 22 23 // 24 public static StaticInitSingleton Instance 25 { 26 get { return instance; } 27 } 28 } 29 }
延迟初始化
此方法改善了静态初始化中的问题,实例按需创建。即在访问Instance时构造了instance实例。
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace SingletonPattern 8 { 9 /// <summary> 10 /// <para>单例模式:延迟初始化</para> 11 /// </summary> 12 public sealed class LazyLoadingSingleton 13 { 14 //私有构造函数 15 private LazyLoadingSingleton() { } 16 17 //需要的时候加载 18 public static LazyLoadingSingleton Instance 19 { 20 get { return Nested.instance; } 21 } 22 23 //内部类 24 class Nested 25 { 26 static Nested() { } 27 28 internal static readonly LazyLoadingSingleton instance = new LazyLoadingSingleton(); 29 } 30 } 31 }
单例模式与静态类的区别
- 模式仅仅是一种思想,我们可以把静态类看成单件的一种实现,但是要了解,单件是控制实例的,而静态类是没有实例的。
- 静态类并不能够实现接口或继承,而我们所说的单件模式中的类是可以的,当然,我们不建议单件模式的类去继承派生,因为这样可能造成实例的不单一,但这恰恰标明单件模式的类与我们普通的类有相同属性,更能相通,灵活控制(可发展为双件模式或多件模式),而静态类与我们普通的类不同,没有如此的扩展性。
- 单件对象可以灵活维护自身对象的实例化,灵活性更大
- 一个项目中过多的使用静态类会造成环境污染,不好管理以及性能降低。
单例模式注意点
不要实现Icloneable接口或继承自其相关的子类,否则客户程序可以跳过已经隐蔽起来的类构造函数。下面的示例说明通过Icloneable接口的克隆过程导致私有构造函数失效,CLR通过内存结构的复制生成了一个新的实例,最终导致并非单一实例存在。
例:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace SingletonPattern 8 { 9 public class BaseEntity : System.ICloneable 10 { 11 public object Clone() 12 { 13 return this.MemberwiseClone(); 14 } 15 16 } 17 public class Singleton : BaseEntity 18 { 19 //… …. 20 } 21 }
严防序列化。对于远程访问,往往需要把复杂的对象序列化后进行传递,但是序列化本身会导致Singleton特性的破坏,因为序列化事实上完成了Singleton对象的拷贝。所以不能对期望具有Singleton特性的类型声明SerializableAttribute属性。
例:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.IO; 7 8 using System.Runtime.Serialization.Formatters.Binary; 9 10 namespace SingletonPattern 11 { 12 [Serializable] 13 public class SerializableSingleton 14 { 15 //序列化 16 public static string SerializeToString(SerializableSingleton graph) 17 { 18 MemoryStream memoryStream = new MemoryStream(); 19 20 BinaryFormatter sf = new BinaryFormatter(); 21 sf.Serialize(memoryStream, graph); 22 23 Byte[] arrGraph = memoryStream.ToArray(); 24 25 return Convert.ToBase64String(arrGraph); 26 } 27 28 //反序列化 29 public static SerializableSingleton DeserializeFromString(string serializedGraph) 30 { 31 Byte[] arrGraph = Convert.FromBase64String(serializedGraph); 32 33 MemoryStream memoryStream = new MemoryStream(arrGraph); 34 35 BinaryFormatter sf = new BinaryFormatter(); 36 SerializableSingleton obj = (SerializableSingleton)sf.Deserialize(memoryStream); 37 38 return obj; 39 } 40 } 41 }
完整示例
这是一个简单的计数器例子,四个线程同时进行计数。
例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace SingletonPattern { public class CountSigleton { private CountSigleton() { Thread.Sleep(2000); } private int toNum = 0; static CountSigleton uniCounter = new CountSigleton(); public static CountSigleton UniCounter { get { return CountSigleton.uniCounter; } } public void Add() { toNum++; } public int GetCounter() { return toNum; } } }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace SingletonPattern 8 { 9 public class CountMutilThread 10 { 11 public CountMutilThread() { } 12 13 public static void DoSomeWork() 14 { 15 string results = ""; 16 CountSigleton MyCounter = CountSigleton.UniCounter; 17 18 for (int i = 0; i < 5; i++) 19 { 20 MyCounter.Add(); 21 results += "线程"; 22 results += Thread.CurrentThread.Name.ToString() + "——〉"; 23 results += "当前的计数:"; 24 results += MyCounter.GetCounter().ToString(); 25 results += " "; 26 27 Console.WriteLine(results); 28 results = ""; 29 } 30 } 31 32 public void StartMain() 33 { 34 Thread thread0 = Thread.CurrentThread; 35 thread0.Name = "Thread0"; 36 37 Thread thread1 = new Thread(new ThreadStart(DoSomeWork)); 38 thread1.Name = "Thread1"; 39 40 Thread thread2 = new Thread(new ThreadStart(DoSomeWork)); 41 thread2.Name = "thread2"; 42 43 Thread thread3 = new Thread(new ThreadStart(DoSomeWork)); 44 thread3.Name = "thread3"; 45 46 thread1.Start(); 47 48 thread2.Start(); 49 50 thread3.Start(); 51 52 /**/ 53 ///线程0也只执行和其他线程相同的工作 54 DoSomeWork(); 55 } 56 } 57 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Threading; 7 8 namespace SingletonPattern 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 CountMutilThread cmt = new CountMutilThread(); 15 cmt.StartMain(); 16 Console.ReadLine(); 17 } 18 } 19 }