• 我的设计模式系列----单例模式


    一、什么叫单例模式

           “单例”从字段意思来讲,故明思议其实就是只有一个实例,那结合面向对象来讲,就是针对一个对象来说,它对外只有一个实例。

            单例模式(Signleton)---是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例(百度百科)

    二、单例模式的目的

           保证一个类仅有一个实例,并提供一个访问它的全局访问点

          

           说明 :图中GetInstanc()方法即为拿到的唯一入口点,该UML图中只有该方法以public修饰,向外公开,并返回对应Singleton对象

           那么我们可以得出结论:

           1.  单例类自己保存它的唯一实例----------------------其实就是得到的静态的instance

           2.  单例类保证没有其他实例能被创建出来----------只有一个GetInstance()是公开的,Single()是私有构造,只能从GetInstance()创建,在这里保证没有其他实例

           3.  单例类可以向外提供一个访问该实例的入口----GetInstance()方法

    三、单例模式的应用常景

           例如:

            1.  windows操作系统里的资源管理都是由资源管理器里统一显示

            2.  一些小公司的办公室里打印文档都只能通过一台唯一的打印机

            3. 远程过程调用对象继承MarshalByRefObject激活模式WellKnownObjectMode.SingleCall确保客户端代理客户只有一个

    四、单例模式有那几种类型

            1. 一般普通单例(见二中的类图)

            2. 安全线程单例

            3. 双重锁定的单例

            4. 静态初始化单例

            5.延迟初始化(这种基本除非常特殊场景外很少实际应用)

    五、几种单例的具体实现及分析

             1. 普通常见单例

             

       
     1 /// <summary>
     2 /// 普通常见单例
     3 /// </summary>
     4 public class NormalSingleton
     5 {
     6     private static NormalSingleton _instance;
     7 
     8     private NormalSingleton() { }
     9 
    10     public static NormalSingleton GetInstance()
    11     {
    12         if (null == _instance)
    13         {
    14             _instance = new NormalSingleton();
    15         }
    16 
    17         return _instance;
    18     }
    19 }
    NormalSingleton
        客户端调用代码示例:
       
     1 static void Main(string[] args)
     2 {
     3     var normalSingleton = NormalSingleton.GetInstance();
     4     var normalSingleton1 = NormalSingleton.GetInstance();
     5 
     6     if (normalSingleton.Equals(normalSingleton1))
     7     {
     8         Console.WriteLine("normalSingleton与normalSingleton1是同一个对象,HashCode是{0}", normalSingleton.GetHashCode());
     9     }
    10     else
    11     {
    12         Console.WriteLine("normalSingleton与normalSingleton1不是同一个对象");
    13     }
    14 
    15     Console.ReadKey();
    16 }
    NormalSingleton

            结果输出:

            

           但之所以我们叫他叫普通常见单例模式,是因为这里只涉及单线程,试想下如果有多个线程同时来请求该单例,同时去调GetInstance()会出现什么情况呢?

           我们猜想在这多个线程同时去GetInstance时,因为是没有做线程同步的,这里几乎可以想象到当多个线程去判断一个非线程同步的变量_instance时,自然而然的会生成不同的实例化对象,而这明显不是我们要的,可以看下图中结果

     1 class Program
     2 {
     3   static NormalSingleton singleton = null;
     4 
     5   static void Main(string[] args)
     6   {
     7       var thread1 = new Thread(new ThreadStart(GetInstance));
     8       var thread2 = new Thread(new ThreadStart(GetInstance));
     9       thread1.Start();
    10       thread2.Start();
    11 
    12       Console.ReadKey();
    13   }
    14 
    15   static void GetInstance()
    16   {
    17       singleton = NormalSingleton.GetInstance();
    18       Console.WriteLine("singleton->hashCode is {0}", singleton.GetHashCode());
    19   }
    20 }
    MoreThreadInNormalSingleton

    明显此时这2个线程访问的不再是同一个对象了。那么面对这种多线程访问的问题我们该如何处理呢?

    其实这就会引入我们的第2种单例--------基于线程安全的单例

       2. 线程安全单例

       我们尝试引入一个线程辅助对象锁lock(其实本质是实现了Moniter.Entry及Moniter.Exit,这个通过IL代码是可以看到的),这样在一个线程进入临界区lock(obj)代码块后,在该部分代码块未执行完成前,另一个线程是不会进入到该临界区代码块的,直至上一个线程执行完lock代码块后,才被允许进入(本质是前一线程进入lock块后,其他对象是不允许访问该lock的对象而只能等待直至上一线程释放该辅助对象)

       OK,我们可以先更改代码,看下效果

       首先,更改NormalSingleton至ThreadSingleton

     1 public class ThreadSingleton
     2 {
     3   /// <summary>
     4   /// 线程辅助对象
     5   /// </summary>
     6   private static readonly object obj = new object();
     7   private static ThreadSingleton _instance;
     8 
     9   private ThreadSingleton() { }
    10 
    11   public static ThreadSingleton GetInstance()
    12   {
    13       lock (obj)
    14       {
    15           if (null == _instance)
    16           {
    17               _instance = new ThreadSingleton();
    18           }
    19       }
    20 
    21       return _instance;
    22   }
    23 }
    ThreadSingleton

    更改原客户端中的NormalSingleton为ThreadSingleton

     1 class Program
     2 {
     3   static ThreadSingleton singleton = null;
     4 
     5   static void Main(string[] args)
     6   {
     7       var thread1 = new Thread(new ThreadStart(GetInstance));
     8       var thread2 = new Thread(new ThreadStart(GetInstance));
     9       thread1.Start();
    10       thread2.Start();
    11 
    12       Console.ReadKey();
    13   }
    14 
    15   static void GetInstance()
    16   {
    17       singleton = ThreadSingleton.GetInstance();
    18       Console.WriteLine("singleton->hashCode is {0}", singleton.GetHashCode());
    19   }
    20 }
    Program-->ThreadSingleton

    运行结果:

    我们看到这时候,2个线程返回的是同一个对象了,那有同学可能疑惑我这2个线程是不是真的一个释放了obj对象另一个线程才访问lock代码块的呢?

    OK,我们可以把每个线程的hashcode及开始结束时间打印出来,这样我相信大家就会明了并确信确定是前一线程执行完lock块后后一线程才开始执行的

    更改ThreadSingleton如下:

     1 public class ThreadSingleton
     2 {
     3   /// <summary>
     4   /// 线程辅助对象
     5   /// </summary>
     6   private static readonly object obj = new object();
     7   private static ThreadSingleton _instance;
     8 
     9   private ThreadSingleton() { }
    10 
    11   public static ThreadSingleton GetInstance()
    12   {
    13       lock (obj)
    14       {
    15           // print current thread hashcode & start time
    16           Console.WriteLine("Current thread num is {0}, startTime is {1}", Thread.CurrentThread.GetHashCode(), DateTime.Now);
    17           if (null == _instance)
    18           {
    19               _instance = new ThreadSingleton();
    20           }
    21           // print current thread hashcode & end time & singleton instance hashcode
    22           Console.WriteLine("Current thread num is {0}, EndTime is {1}, Instance hashcode is {2}"
    23               , Thread.CurrentThread.GetHashCode(), DateTime.Now, _instance.GetHashCode());
    24       }
    25 
    26       return _instance;
    27   }
    28 }
    ThreadSingleton

    CTRL+ F5后

      其实第2种模式中,我们发现一个问题,就是我们不管单例类的实例是不是已经有了(_instance是否为null),我们都强行先锁定然后再判断,这样相对为说对对系统资源开消来说可能也是一种浪费,试想如果第2个线程在进来之前发现当前_instance已经被实例化了,我们为毛还要继续去锁定辅助对象obj,这不是浪费吗???

      OK,鉴于此,我们是不是考虑在进行lock(obj)前看如果_instance已经实例化了,我们直接返回,是不是会比第2种情况节约很多系统开销? 对,这就是我们接着要讲的第3种单例模式------基于双重锁定的单例

       3. 双重锁定单例

          第一次线程在对象未初始化时,去判断对象是否已实例化,如果已经实例化,则直接返回,不用每次去加锁操作,减少性能损耗,如果未实例化,则再走2(线程安全的单例)

     1 public class DoubleLockSingleton
     2 {
     3   /// <summary>
     4   /// 线程辅助对象
     5   /// </summary>
     6   private static readonly object obj = new object();
     7   private static DoubleLockSingleton _instance;
     8 
     9   private DoubleLockSingleton() { }
    10 
    11   public static DoubleLockSingleton GetInstance()
    12   {
    13       // first to judge the instance is inited, if ok then return instance
    14       if (null == _instance)
    15       {
    16           Console.WriteLine("Current instance is not inited ? {0}", null == _instance);
    17           // if the instance is not inited then lock the object to avoid other thread into this code regein when curren thread accesses the code
    18           lock (obj)
    19           {
    20               // print current thread hashcode & start time
    21               Console.WriteLine("Current thread num is {0}, startTime is {1}", Thread.CurrentThread.GetHashCode(), DateTime.Now);
    22               if (null == _instance)
    23               {
    24                   _instance = new DoubleLockSingleton();
    25               }
    26               // print current thread hashcode & end time & singleton instance hashcode
    27               Console.WriteLine("Current thread num is {0}, EndTime is {1}, Instance hashcode is {2}"
    28                   , Thread.CurrentThread.GetHashCode(), DateTime.Now, _instance.GetHashCode());
    29           }
    30       }
    31 
    32       return _instance;
    33   }
    34 }
    DoubleLockSingleton

          

         修改客户端如下:

     1 class Program
     2 {
     3   static DoubleLockSingleton singleton = null;
     4 
     5   static void Main(string[] args)
     6   {
     7       var thread1 = new Thread(new ThreadStart(GetInstance));
     8       var thread2 = new Thread(new ThreadStart(GetInstance));
     9       thread1.Start();
    10       thread2.Start();
    11       Thread.Sleep(1000);
    12       var thread3 = new Thread(new ThreadStart(GetInstance));
    13       thread3.Start();
    14 
    15       Console.ReadKey();
    16   }
    17 
    18   static void GetInstance()
    19   {
    20       singleton = DoubleLockSingleton.GetInstance();
    21       Console.WriteLine("singleton->hashCode is {0}", singleton.GetHashCode());
    22   }
    23 }
    Program->DoubleLockSingleton

    其实在我们实际应用中,C#本身也提供了一套“静态初始化”方法,这种方法不需要编写线程安全代码,来我们看看静态初始化单例

    4. 静态初始化单例

       公共运行库提供静态初始化单例,一种密封类,防止派生类的产生,变量采用readonly修饰,只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量

     1 public sealed class SealdSingleton
     2 {
     3   private static readonly SealdSingleton _instance = new SealdSingleton();
     4   private SealdSingleton() { }
     5 
     6   public static SealdSingleton GetInstance()
     7   {
     8       return _instance;
     9   }
    10 }
    SealdSingleton

       

       修改客户端代码,还是用SealdSingleton替换原来singleton,Ctrl + F5效果如下:

      5. 延迟初始化单例

         延迟初始化采用内部类的方式,在内部类中初始化该对象

     1 public class NestedSingleton
     2 {
     3   NestedSingleton() { }
     4 
     5   public static NestedSingleton Instance
     6   {
     7       get
     8       {
     9           return NestedClass.instance;
    10       }
    11   }
    12 
    13   class NestedClass
    14   {
    15       NestedClass() { }
    16 
    17       internal static readonly NestedSingleton instance = new NestedSingleton();
    18   }
    19 }
    NestedSingleton

         

         同时客户端修改:用NestedSingleton替换single,然后Ctrl + F5

       但因为此种实际应用中几乎很少使用,故不作推荐

       至此,整个单例模式讲解完毕,相关代码稍后会提交至github及csdn中,有兴趣可以下载调试研究

       git地址:https://gitee.com/jimmyTown_admin_admin/SingletonPattern

  • 相关阅读:
    POJ 1228 Grandpa's Estate | 凸包
    POJ 2187 Beauty Contest | 旋转卡壳
    POJ 3348 Cows | 凸包模板题
    POJ 1375 Intervals | 解析几何
    POJ 2074 | 线段相交
    POJ 1039 Pipe | 线段相交
    POJ 3304 Segments | 线段相交
    POJ 2318 TOYS | 二分+判断点在多边形内
    jpg、jpeg、png... 的区别
    xhr.readyState就绪状态
  • 原文地址:https://www.cnblogs.com/AreaWhere/p/9327204.html
Copyright © 2020-2023  润新知