Singleton 模式

类别: 设计模式 查看评论

顾名思义,Singleton模式就是确保一个类只能有唯一的一个实例。Singleton模式用于对象的创建,这就意味着,如果某个类采用了Singleton模式,那么这个类被创建后,只能有一个实例可以访问。

Singleton模式的特点

  1. Singleton类有且仅有一个实例
  2. Singleton类在第一次被调用的时候创建这个唯一的实例
  3. Singleton类被再次调用的时候提供这一实例

应用场景

很多时候,都需要使用Singleton模式,例如希望在整个应用程序中只有一个连接数据库的实例,或者对文件、目录的操作等等

简单分析

  1. 定义一个静态的私有变量,来标识是否已经产生过实例
  2. 不直接使用类的构造函数,而是提供一个静态的方法来构造类的实例
  3. 将类的构造函数设置为Private从而将构造函数隐藏起来,以防止通过调用构造函数来产生类的实例

具体实现

实现Singleton模式有几种方法。

方法1:懒实例化

class SingletonDemo1
{
    private static SingletonDemo1 instance = null;
 
    private SingletonDemo1() { }
 
    public static SingletonDemo1 GetInstance()
    {
        if (null == instance)
        {
            instance = new SingletonDemo1();
        }
        return instance;
    }
}

GetInstance 方法也可以用属性来实现:

class SingletonDemo1
{
    private static SingletonDemo1 instance = null;
    private SingletonDemo1() { }
 
    public SingletonDemo1 Instance
    {
        get
        {
            if (null==instance)
            {
                instance = new SingletonDemo1();
            }
            return instance;
        }
    }
}

这种方法称为”懒实例化”,直到对象要求产生一个实例时才执行实例化,懒实例化避免了在应用程序启动时实例化不必要的 singleton。

由于实例是在 Instance 属性或者GetInstance方法内部创建的,因此类可以使用附加功能(例如,对子类进行实例化),即使它可能引入不想要的依赖性。

但这种方法有个问题,在多线程情况是下是不安全的,如果执行过程中多个线程同时进入Instance 属性或者GetInstance方法就有可能产生多个实例。解决此问题的方法有很多,一种方法是使用被称为 Double-Check Locking的技术。而 C# 与公共语言运行库也提供了一种”静态初始化”方法,这种方法不需要开发人员显式地编写线程安全代码,即可解决这些问题。

方法二:静态初始化

public sealed class SingletonDemo2
{
    private static readonly SingletonDemo2 instance = new SingletonDemo2();
    private SingletonDemo2() { }
 
    public static SingletonDemo2 GetInstance()
    {
        return instance;
    }
}

在次策略中:

  1. 将class标记为sealed以阻止派生,因为派生类可能增加实例,而这是不希望发生的;
  2. 将变量instance标记为readonly,则只能在静态初始化期间,以及类的构造函数中对分配变量;
  3. 在第一次引用类的任何成员的时候创建实例,由CLR来负责处理变量的初始化

Singleton的实例被类的静态变量引用,因为在第一次调用GetInstance方法前,不会有实例产生,这个方法也是懒实例化的一种形式。

由于这种方法有.Net fx负责初始化,失去了对实例化的控制,例如不能够在实力化之前使用非默认的构造函数或者做些其他的操作。

方法三:double-check locking

如果需要延迟实例化,在实例化前执行一些其他操作,或者可能工作在多线程情况下,就需要一种其他方法来满足需求,通常使用double-chekc locking方法:

public sealed class SingletonDemo3
{
    private static volatile SingletonDemo3 instance;
    private static object syncLock = new object();
    private SingletonDemo3() { }
 
    public Static SingletonDemo3 GetInstance()
    {
        if (null == instance)
        {
            lock (syncLock)
            {
                if (null == instance)
                    instance = new SingletonDemo3();
            }
        }
        return instance;
    }
}

这个方法解决了线程并发的问题,同时避免在每个GetInstance方法调用中出现独占锁定.

  1. 此方法能确保在需要时创建一个唯一的实例
  2. 变量名声明为volatile,来通知编译器,将有多个线程访问instance,因为不应对该instance的状态做任何优化假设。当一个变量被定义为volatile的时候,将每次都从内存中读取数据,而不是从缓存、寄存器中。
    不过volatile只使用于单处理器的情况,如果是多处理器情况就不一样了[via]
  3. 使用syncLock来进行锁定,确保当一个线程位于代码的临界区时,另一个线程不进入临界区
下一篇:

发表评论