• (一)单例模式详解


      模式是一个非常有趣的话题,它是对特定前提下重复出现问题的一个普遍解答,它是一种思想,使用得当也会对设计、实施提供帮助。
      简单的说,软件开发发展了几十年,前人遇到了很多很多的问题,有些人做了归纳总结,把某一类问题总结出一个解决套路,这些套路可以有效的解决类似的问题。形成了我们的23种模式。

    概述
      Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点[DP]。
      单例模式(Singleton)结构图

    多线程时的单例
      Lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它一直将等待(即被阻止),直到该对象被释放。[MSDN]

    五种实现

    (一)简单实现

    • 简单实现很容易理解,只有在实例为空的情况下才创建新的实例。
    • 但若处在多线程情况下,会出现问题:
    1. 线程一执行完if(instance==null)这句后,准备进入下一句创建实例时,被挂起;
    2. 此时切换到线程二,线程二全部执行完毕后,实例已经创建完毕。
    3. 线程一激活,继续创建实例,造成双实例情况。违背单间原则。

    代码如下:

    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 }

  • 相关阅读:
    DataType--数值类型
    Scala中List(Map1,Map2,Map3 ....) 转成一个Map
    Scala中集合类型与java中集合类型转换
    oracle查询数据库最大连接数等信息
    kafka_2.11-0.10.2.1中的auto.offset.reset
    IOS设备信息与机型对照表
    shell 后台执行脚本
    Spark的操作列表
    hive表支持中文设置
    编译Spark2.1.2源码
  • 原文地址:https://www.cnblogs.com/armyant/p/3263797.html
Copyright © 2020-2023  润新知