Singleton设计模式的C#实现

Singleton模式   Singleton(译为单件或单态)模式是设计模式中比较简单而常用的模式  有些时候在整个应用程序中 会要求某个类有且只有一个实例 这个时候可以采用Singleton模式进行设计 用Singleton模式设计的类不仅能保证在应用中只有一个实例 而且提供了一种非全局变量的方法进行全局访问 称为全局访问点 这样对于没有全局变量概念的纯面向对象语言来说是非常方便的 比如C#  本文用一个计数器的例子来描述在C#中如何使用Singleton模式 计数的值设计为计数器类的一个私有成员变量 它被 个不同的线程进行读写操作 为保证计数的正确性 在整个应用当中必然要求计数器类的实例是唯一的 Singleton的实现方式   首先看看教科书方式的Singleton标准实现的两种方法 以下用的是类C#伪代码  方法一 using System; namespace csPattern Singleton { public class Singleton { static Singleton uniSingleton = new Singleton(); private Singleton() {} static public Singleton instance() { return uniSingleton; } } } 方法二 using System; namespace csPattern Singleton { public class Singleton { static Singleton uniSingleton; private Singleton() {} static public Singleton instance() { if (null == uniSingleton) { uniSingleton = new Singleton _lazy(); } return uniSingleton; } } } Singleton模式的实现有两个技巧 一是使用静态成员变量保存 全局 的实例 确保了唯一性 使用静态的成员方法instance() 代替 new关键字来获取该类的实例 达到全局可见的效果 二是将构造方法设置成为private 如果使用new关键字创建类的实例 则编译报错 以防编程时候笔误? 上面方法二的初始化方式称为lazy initialization 是在第一次需要实例的时候才创建类的实例 与方法一中类的实例不管用不用一直都有相比 方法二更加节省系统资源 但是方法二在多线程应用中有时会出现多个实例化的现象 假设这里有 个线程 主线程和线程 在创建类的实例的时候可能会遇到一些原因阻塞一段时间(比如网络速度或者需要等待某些正在使用的资源的释放) 此时的运行情况如下 主线程首先去调用instance()试图获得类的实例 instance()成员方法判断该类没有创建唯一实例 于是开始创建实例 由于一些因素 主线程不能马上创建成功 而需要等待一些时间 此时线程 也去调用instance()试图获得该类的实例 因为此时实例还未被主线程成功创建 因此线程 又开始创建新实例 结果是两个线程分别创建了两次实例 对于计数器类来说 就会导致计数的值被重置 与Singleton的初衷违背 解决这个问题的办法是同步  下面看看本文的计数器的例子的实现  使用方法一 using System; using System Threading; namespace csPattern Singleton { public class Counter { static Counter uniCounter = new Counter(); //存储唯一的实例 private int totNum = ; //存储计数值 private Counter() { Thread Sleep( ); //这里假设因为某种因素而耽搁了 毫秒 //在非lazy initialization 的情况下 不会影响到计数 } static public Counter instance() { return uniCounter; } public void Inc() { totNum ++;} //计数加 public int GetCounter() { return totNum;} //获得当前计数值 } } 以下是调用Counter类的客户程序 在这里我们定义了四个线程同时使用计数器 每个线程使用 次 最后得到的正确结果应该是 using System; using System IO; using System Threading; namespace csPattern Singleton MutileThread { public class MutileClient { public MutileClient() {} public void DoSomeWork() { Counter myCounter = Counter instance(); //方法一 //Counter_lazy myCounter = Counter_lazy instance(); //方法二 for (int i = ; i < ; i++) { myCounter Inc(); Console WriteLine( 线程{ }报告: 当前counter为: { } Thread CurrentThread Name ToString() myCounter GetCounter() ToString()); } } public void ClientMain() { Thread thread = Thread CurrentThread; thread Name = Thread ; Thread thread =new Thread(new ThreadStart(this DoSomeWork)); thread Name = Thread ; Thread thread =new Thread(new ThreadStart(this DoSomeWork)); thread Name = Thread ; Thread thread =new Thread(new ThreadStart(this DoSomeWork)); thread Name = Thread ; thread Start(); thread Start(); thread Start(); DoSomeWork(); //线程 也只执行和其他线程相同的工作 } } }  以下为Main函数 本程序的测试入口 using System; namespace csPattern Singleton { public class RunMain { public RunMain() {} static public void Main(string[] args) { MutileThread MutileClient myClient = new MutileThread MutileClient(); myClient ClientMain(); System Console ReadLine(); } } }  执行结果如下 线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  线程Thread 报告: 当前counter为:  由于系统线程调度的不同 每次的执行结果也不同 但是最终结果一定是  方法一中由于实例一开始就被创建 所以instance()方法无需再去判断是否已经存在唯一的实例 而返回该实例 所以不会出现计数器类多次实例化的问题 使用方法二 using System; using System Threading; using System Runtime CompilerServices; namespace csPattern Singleton { public class Counter_lazy { static Counter_lazy uniCounter; private int totNum = ; private Counter_lazy() { Thread Sleep( ); //假设多线程的时候因某种原因阻塞 毫秒 } [MethodImpl(MethodImplOptions Synchronized)] //方法的同步属性 static public Counter_lazy instance() { if (null == uniCounter) { uniCounter = new Counter_lazy(); } return uniCounter; } public void Inc() { totNum ++;} public int GetCounter() { return totNum;} } } 不知道大家有没有注意到instance()方法上方的[MethodImpl(MethodImplOptions Synchronized)] 语句 他就是同步的要点 他指定了instance()方法同时只能被一个线程使用 这样就避免了线程 调用instance()创建完成实例前线程 就来调用instance()试图获得该实例  根据MSDN的提示 也可以使用lock关键字进行线程的加锁 代码如下 using System; using System Threading; namespace csPattern Singleton { public class Counter_lazy { static Counter_lazy uniCounter; static object myObject = new object(); private int totNum = ; private Counter_lazy() { Thread Sleep( ); //假设多线程的时候因某种原因阻塞 毫秒 } static public Counter_lazy instance() { lock(myObject) { if (null == uniCounter) { uniCounter = new Counter_lazy(); } return uniCounter; } } public void Inc() { totNum ++;} public int GetCounter() { return totNum;} } } lock()是对一个对象加互斥锁 只允许一个线程访问其后大括号中语句块 直到该语句块的代码执行完才解锁 解锁后才允许其他的线程执行其语句块 还可以使用Mutex类进行同步 定义private static Mutex mut = new Mutex();后 修改instance()如下 同样可以得到正确的结果 static public Counter_lazy instance() { mut WaitOne(); if (null == uniCounter) { uniCounter = new Counter_lazy(); } mut ReleaseMutex(); return uniCounter; } 注意的是 本例中使用方法二要更改方法一的客户程序 去掉Counter_lazy intance()的注释 并将Counter intance()注释 singleton模式还可以拓展 只要稍加修改 就可以限制在某个应用中只能允许m个实例存在 而且为m个实例提供全局透明的访问方法 lishixinzhi/Article/program/net/201311/12545