zoukankan      html  css  js  c++  java
  • Singleton设计模式的C#实现(外摘)

    Singleton模式 

       Singleton(译为单件或单态)模式是设计模式中比较简单而常用的模式。

       有些时候在整个应用程序中,会要求某个类有且只有一个实例,这个时候可以采用Singleton模式进行设计。用Singleton模式设计的类不仅能保证在应用中只有一个实例,而且提供了一种非全局变量的方法进行全局访问,称为全局访问点,这样对于没有全局变量概念的纯面向对象语言来说是非常方便的,比如C#

       本文用一个计数器的例子来描述在C#中如何使用Singleton模式:计数的值设计为计数器类的一个私有成员变量,它被4个不同的线程进行读写操作,为保证计数的正确性,在整个应用当中必然要求计数器类的实例是唯一的。

    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,是在第一次需要实例的时候才创建类的实例,与方法一中类的实例不管用不用一直都有相比,方法二更加节省系统资源。但是方法二在多线程应用中有时会出现多个实例化的现象。
       假设这里有2个线程:主线程和线程1,在创建类的实例的时候可能会遇到一些原因阻塞一段时间(比如网络速度或者需要等待某些正在使用的资源的释放),此时的运行情况如下:

       主线程首先去调用instance()试图获得类的实例,instance()成员方法判断该类没有创建唯一实例,于是开始创建实例。由于一些因素,主线程不能马上创建成功,而需要等待一些时间。此时线程1也去调用instance()试图获得该类的实例,因为此时实例还未被主线程成功创建,因此线程1又开始创建新实例。结果是两个线程分别创建了两次实例,对于计数器类来说,就会导致计数的值被重置,与Singleton的初衷违背。解决这个问题的办法是同步。

       下面看看本文的计数器的例子的实现:

       使用方法一:

    using System; 
    using System.Threading;
    namespace csPattern.Singleton
    {
    public class Counter
    {
    static Counter uniCounter = new Counter(); //存储唯一的实例。
    private int totNum = 0; //存储计数值。
    private Counter()
    {
    Thread.Sleep(100); //这里假设因为某种因素而耽搁了100毫秒。
    //在非lazy initialization 的情况下, 不会影响到计数。.
    }
    static public Counter instance()
    {
    return uniCounter;
    }
    public void Inc() { totNum ++;} //计数加1。
    public int GetCounter() { return totNum;} //获得当前计数值。
    }
    }

       以下是调用Counter类的客户程序,在这里我们定义了四个线程同时使用计数器,每个线程使用4次,最后得到的正确结果应该是16:

    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 = 1; i < 5; i++)
    {
    myCounter.Inc();
    Console.WriteLine("线程{0}报告: 当前counter为: {1}", Thread.CurrentThread.Name.ToString(), myCounter.GetCounter().ToString());
    }
    }
    public void ClientMain()
    {
    Thread thread0 = Thread.CurrentThread;
    thread0.Name = "Thread 0";
    Thread thread1 =new Thread(new ThreadStart(this.DoSomeWork));
    thread1.Name = "Thread 1";
    Thread thread2 =new Thread(new ThreadStart(this.DoSomeWork));
    thread2.Name = "Thread 2";
    Thread thread3 =new Thread(new ThreadStart(this.DoSomeWork));
    thread3.Name = "Thread 3";
    thread1.Start();
    thread2.Start();
    thread3.Start();
    DoSomeWork(); //线程0也只执行和其他线程相同的工作。
    }
    }
    }

       以下为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 1报告: 当前counter为: 2
       线程Thread 1报告: 当前counter为: 4
       线程Thread 1报告: 当前counter为: 5
       线程Thread 1报告: 当前counter为: 6
       线程Thread 3报告: 当前counter为: 7
       线程Thread 3报告: 当前counter为: 8
       线程Thread 3报告: 当前counter为: 9
       线程Thread 3报告: 当前counter为: 10
       线程Thread 0报告: 当前counter为: 1
       线程Thread 0报告: 当前counter为: 11
       线程Thread 0报告: 当前counter为: 12
       线程Thread 0报告: 当前counter为: 13
       线程Thread 2报告: 当前counter为: 3
       线程Thread 2报告: 当前counter为: 14
       线程Thread 2报告: 当前counter为: 15
       线程Thread 2报告: 当前counter为: 16

       由于系统线程调度的不同,每次的执行结果也不同,但是最终结果一定是16。

       方法一中由于实例一开始就被创建,所以instance()方法无需再去判断是否已经存在唯一的实例,而返回该实例,所以不会出现计数器类多次实例化的问题。
       使用方法二:

    using System; 
    using System.Threading;
    using System.Runtime.CompilerServices;
    namespace csPattern.Singleton
    {
    public class Counter_lazy
    {
    static Counter_lazy uniCounter;
    private int totNum = 0;
    private Counter_lazy()
    {
    Thread.Sleep(100); //假设多线程的时候因某种原因阻塞100毫秒
    }
    [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()方法同时只能被一个线程使用,这样就避免了线程0调用instance()创建完成实例前线程1就来调用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 = 0;
    private Counter_lazy()
    {
    Thread.Sleep(100); //假设多线程的时候因某种原因阻塞100毫秒
    }
    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个实例提供全局透明的访问方法。
  • 相关阅读:
    chrome浏览器中安装以及使用Elasticsearch head 插件
    windows10 升级并安装配置 jmeter5.3
    linux下部署Elasticsearch6.8.1版本的集群
    【Rollo的Python之路】Python 爬虫系统学习 (八) logging模块的使用
    【Rollo的Python之路】Python 爬虫系统学习 (七) Scrapy初识
    【Rollo的Python之路】Python 爬虫系统学习 (六) Selenium 模拟登录
    【Rollo的Python之路】Python 爬虫系统学习 (五) Selenium
    【Rollo的Python之路】Python 爬虫系统学习 (四) XPath学习
    【Rollo的Python之路】Python 爬虫系统学习 (三)
    【Rollo的Python之路】Python sys argv[] 函数用法笔记
  • 原文地址:https://www.cnblogs.com/HondaHsu/p/710784.html
Copyright © 2011-2022 走看看