(一)单例模式详解

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

原文地址:https://www.cnblogs.com/armyant/p/3263797.html