zoukankan      html  css  js  c++  java
  • (一)单例模式详解

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

  • 相关阅读:
    实例属性 类属性 实例域 类域
    研究数据集
    static 静态域 类域 静态方法 工厂方法 he use of the static keyword to create fields and methods that belong to the class, rather than to an instance of the class 非访问修饰符
    accessor mothod mutator mothod 更改器方法 访问器方法 类的方法可以访问类的任何一个对象的私有域!
    上钻 下钻 切片 转轴 降采样
    识别会话
    Performance Tuning Using Linux Process Management Commands
    Secure Hash Algorithm 3
    grouped differently across partitions
    spark 划分stage Wide vs Narrow Dependencies 窄依赖 宽依赖 解析 作业 job stage 阶段 RDD有向无环图拆分 任务 Task 网络传输和计算开销 任务集 taskset
  • 原文地址:https://www.cnblogs.com/armyant/p/3263797.html
Copyright © 2011-2022 走看看