[设计模式学习笔记] 创建型模式之Singleton

一、Singleton模式的特点:

  • Singleton类只能有一个实例。
  • Singleton类必须自己创建自己的唯一实例。
  • Singleton类必须给所有其它对象提供这一实例,即为该实例提供一个全局访问点。

二、Singleton模式的结构

Singleton拥有一个私有构造函数,确保用户无法通过new直接实例化它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance方法负责检验并实例化自己,然后把实例存储在静态成员变量中。

三、程序举例

using System;
using System.Collections.Generic;
using System.Threading;

namespace SingletonExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ParameterizedThreadStart ts = newParameterizedThreadStart(EnterPlayer);
            for (int i = 0; i < 20; i++)
            {               
                Thread t = new Thread(ts);
                t.Start("player" + i);
            }

            LoadBalanceServer.GetLoadBalanceServer().ShowServerInfo();

        }

        static void EnterPlayer(object playerName)
        {
            LoadBalanceServer lbs = LoadBalanceServer.GetLoadBalanceServer();
            lbs.GetLobbyServer().EnterPlayer(playerName.ToString());
        }
    }

    class LoadBalanceServer
    {
        private const int SERVER_COUNT = 3;
        private List<LobbyServer> serverList = new List<LobbyServer>();

        private static volatile LoadBalanceServer lbs;
        private static object syncLock = new object();

        private LoadBalanceServer()
        {
            for (int i = 0; i < SERVER_COUNT; i++)
            {
                serverList.Add(new LobbyServer("LobbyServer" + i));
            }
        }

        public static LoadBalanceServer GetLoadBalanceServer()
        {
            if (lbs == null)
            {
                lock (syncLock)
                {
                    if (lbs == null)
                    {
                        Thread.Sleep(100);
                        lbs = new LoadBalanceServer();
                    }
                }
            }
            return lbs;
        }

        public LobbyServer GetLobbyServer()
        {
            LobbyServer ls = serverList[0];
            for (int i = 1; i < SERVER_COUNT; i++)
            {
                if (serverList[i].PlayerList.Count < ls.PlayerList.Count)
                    ls = serverList[i];
            }
            return ls;
        }

        public void ShowServerInfo()
        {
            foreach (LobbyServer ls in serverList)
            {
                Console.WriteLine("=================" + ls.ServerName +"=================");
                foreach (string player in ls.PlayerList)
                {
                    Console.WriteLine(player);
                }
            }
        }
    }

    class LobbyServer
    {
        private List<string> playerList = new List<string>();

        public List<string> PlayerList
        {
            get { return playerList; }
        }

        private string serverName;

        public string ServerName
        {
            get { return serverName; }
        }

        public LobbyServer(string serverName)
        {
            this.serverName = serverName;
        }

        public void EnterPlayer(string playerName)
        {
            playerList.Add(playerName);
        }
    }
}

代码说明

  • LoadBalanceServer类实现了Singleton模式,也就是说无论在什么情况下,只会有一个LoadBalanceServer类的实例出现。
  • LobbyServer类表示大厅服务,用户进入大厅后和大厅服务进行服务,在这里我们仅仅在大厅服务里面保存了用户列表。
  • Singleton模式有很多实现方式,在这里使用的是双重锁定方式。对于C#来说,可能使用静态初始化方式是最简洁的,这里就不演示了。
  • LoadBalanceServer类的GetLobbyServer()方法负责返回一个压力最小的LobbyServer对象。
  • 实例化LoadBalanceServer的时候Sleep了线程,目的是模拟高并发的情况,在正式代码中没有必要这样做。

四、在什么情形下使用Singleton模式

使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例模式。 从应用角度来说,你希望有一个总管来负责某一件事情。并且这件事情的分配只能有一个人进行,如果有多个人进行肯定会弄乱。比如创建处理流水号如果有两个地方在创建的话是不是就会重复了呢? 注意: 不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。 不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。

五、C#中的Singleton模式

C#的独特语言特性决定了C#拥有实现Singleton模式的独特方法。这里不再赘述原因,给出几个结果:

方法一:静态初始化

下面是利用.NET Framework平台优势实现Singleton模式的代码:

sealed class Singleton
{
  private Singleton();

  public static readonly Singleton Instance=new Singleton();
}

这使得代码减少了许多,同时也解决了线程问题带来的性能上损失。那么它又是怎样工作的呢?

注意到,Singleton类被声明为sealed,以此保证它自己不会被继承,其次没有了Instance的方法,将原来_instance成员变量变成public readonly,并在声明时被初始化。通过这些改变,我们确实得到了Singleton的模式,原因是在JIT的处理过程中,如果类中的static属性被任何方法使用时,.NET Framework将对这个属性进行初始化,于是在初始化Instance属性的同时Singleton类实例得以创建和装载。而私有的构造函数和readonly(只读)保证了Singleton不会被再次实例化,这正是Singleton设计模式的意图。

(摘自:http://www.cnblogs.com/huqingyu/archive/2004/07/09/22721.aspx )

不过这也带来了一些问题,比如无法继承,实例在程序一运行就被初始化,无法实现延迟初始化等。

详细情况可以参考微软MSDN文章:《Exploring the Singleton Design Pattern》

方法二:静态初始化变种

既然方法一存在问题,我们还有其它办法。

public sealed class Singleton
{
   Singleton()   {   }

   public static Singleton GetInstance()
   {
      return Nested.instance;
   }

  class Nested   {
  // Explicit static constructor to tell C# compiler
  // not to mark type as beforefieldinit
  static Nested()
  {
  }

  internal static readonly Singleton instance = new Singleton();
  }
}

这实现了延迟初始化,并具有很多优势,当然也存在一些缺点。详细内容请访问:《Implementing the Singleton Pattern in C#》。文章包含五种Singleton实现,就模式、线程、效率、延迟初始化等很多方面进行了详细论述。

静态初始化潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。

方法三:多线程Singleton

静态初始化适合于大多数情形。如果您的应用程序必须延迟实例化、在实例化之前使用非默认的构造函数或执行其他任务、并且工作在多线程环境中,那么您需要另一种解决方案。但是,在一些情况下,您无法像在”静态初始化”示例中那样依赖公共语言运行库来确保线程的安全性。在这种情况下,必须使用特定的语言功能来确保在存在多线程的情况下仅创建一个对象实例。更常见的解决方案之一是使用 Double-Check Locking(双重锁定)技术来阻止不同的线程同时创建 singleton 的新实例。

using System;
public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();
   private Singleton() {}

   public static Singleton Instance
   {
     get
     {
       if (instance == null)
       {
          lock (syncRoot)
          {
            if (instance == null)
              instance = new Singleton();
          }
       }
       return instance;
      }
   }
}

此方法确保了仅在需要实例时才会创建仅一个实例。此外,变量被声明为 volatile,以确保只有在实例变量分配完成后才能访问实例变量。最后,此方法使用 syncRoot 实例来进行锁定(而不是锁定类型本身),以避免发生死锁。

此 double-check locking 方法解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类型的实现。大多数情况下,静态初始化方法已经够用。

原创文章,转载请注明: 转载自闲云博客

本文链接地址: [设计模式学习笔记] 创建型模式之Singleton