C#设计模式之Singleton模式

2020-06-11 18:00:23王旭

前言

Singleton是二十三个设计模式中比较重要也比较经常使用的模式。但是这个模式虽然简单,实现起来也会有一些小坑,让我们一起来看看吧!

实现思路

首先我们看看这个设计模式的UML类图。

很清晰的可以看到,有三点是需要我们在实现这个模式的时候注意的地方。

私有化的构造器 全局唯一的静态实例 能够返回全局唯一静态实例的静态方法

其中,私有化构造器是防止外部用户创建新的实例而静态方法用于返回全局唯一的静态实例供用户使用。原理清楚了,接下来我们看看一些典型的实现方式和其中的暗坑。

实现方法

最简单的实现方法

最简单的实现方法自然就是按照UML类图直接写一个类,我们看看代码。

 class Program
 {
  static void Main(string[] args)
  {
  	var single1 = Singleton.Instance;
   var single2 = Singleton.Instance;
   Console.WriteLine(object.ReferenceEquals(single1, single2));
   Console.ReadLine();
  }
 }

 class Singleton
 {
  private static Singleton _Instance = null;
  private Singleton()
  {
   Console.WriteLine("Created");
  }

  public static Singleton Instance
  {
   get
   {
    if (_Instance == null)
    {
     _Instance = new Singleton();
    }
    return _Instance;
   }
  }

  public void DumbMethod()
  {

  }
 }

这段代码忠实的实现了UML类图里面的一切,查看输出结果,

证实了Singleton确实起了作用,多次调用仅仅产生了一个实例,似乎这么写就可以实现这个模式了。但是,真的会那么简单吗?

如果多线程乱入?

现在我们给刚刚的例子加点调料,假设多个对实例的调用,并不是简单的,彬彬有礼的顺序关系,二是以多线程的方式调用,那么刚刚那种实现方法,还能从容应对吗?让我们试试。把Main函数里面的调用改成这样。

	static void Main(string[] args)
  {
   int TOTAL = 10000;
   Task[] tasks = new Task[TOTAL];
   for (int i = 0; i < TOTAL; i++)
   {
    tasks[i] = Task.Factory.StartNew(() =>
    {
     Singleton.Instance.DumbMethod();
    });
   }
			Task.WaitAll(tasks);
   Console.ReadLine();
  }

通过Factory创造出1万个Task,几乎同时去请求这个单例,看看输出。

咦,我们刚刚写的Singleton模式失效了,这个类被创造了5次(这段代码运行多次,这个数字不一定相同),一定是多线程搞的鬼,我们刚刚写的代码没有办法应对多线程,换句话说,是非线程安全的(thread-safe),那有没有办法来攻克这个难关呢?