单例模式(Singleton)



保证一个类仅有一个实例,并且提供一个该实例的全局访问点。 ————GoF  

     在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。   用户不能随便的使用*new*去创建一个类,这应该是设计者应该控制的。

简单实现:   1、类内部维护一个静态的变量。   2、将构造函数私有化,让外界不能*new*。   3、创建一个全局的访问点。   问题:对于线程不安全。如果有两个线程同时首次访问这个单例,会同时判断这个访问点的变量,这时该变量为null,这样就会创建两个实例,不符合单例的要求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public sealed class Singleton
{
    static Singleton instance = null;  //类内部维护一个静态的变量
    private Singleton()   //将构造函数私有化,让外界不能*new*
    {
    }
    public static Singleton Instance   //创建一个全局的访问点
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

线程安全的实现:   同一时刻加了锁的部分程序只有一个线程可以进入。   对象的实例由最先进入的那个线程创建。   后来的线程在进入时(instance==null)为假,不会再去创建对象的实例。   增加了额外的开销,损失了性能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();
    private Singleton()
    {
    }
    public static Singleton Instance
    {
        get
        {
            //加锁,当有几个线程同时访问时,使用内部机制安排一个继续执行,其余的等待。
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
            }

            return instance;
        }
    }
}

线程安全的实现(增强版):   增加一个额外的判断,就能避免额外的判断步骤,减少开销。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();
    private Singleton()
    {
    }
    public static Singleton Instance
    {
        get
        {
            //增加一个额外的判断,就能阻止每次都进行加锁的额外步骤
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

静态初始化:   静态构造方法在程序运行的整个过程中只会执行一次,这是.net的内部机制。   静态构造方法会在该类的任何操作发生时触发,包括执行该类中的其他方法、调用其它属性,都会执行静态构造函数。   所以就有一个问题,如果我们需要在创建单例对象之前,执行该类中的其他方法,这时就会自动的执行静态构造方法,而此时我们并不需要这个单例对象,所以这不是一个完美的解决方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public sealed class Singleton
{
    private static readonly Singleton instance = null;
    //在.net中静态构造方法的特性,来创建单例实例
    static Singleton()
    {
        instance = new Singleton(); 
    }
    //私有化构造函数,让外界不能new
    private Singleton() 
    {

    }
    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

延迟初始化:   初始化的工作交给了*BuildSingleton*这个类的静态构造函数,这样就不会像“静态初始化”那样只要操作Singleton类,就会执行静态构造函数,这样就实现了延迟初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public sealed class Singleton
{
    private Singleton()
    {

    }
    public static Singleton Instance
    {
        get
        {
            return BuildSingleton.instance;
        }
    }
    //将静态构造函数放在类中,用到的时候触发自动执行单例对象的初始化。
    private class BuildSingleton
    {
        internal static readonly Singleton instance =null;
        static BuildSingleton()
        {
            instance = new Singleton();
        }
    }
}

总结:   Singleton模式中的实例构造函数可以设置为protected以允许子类派生。   Singleton模式一般不要支持ICloneable接口(克隆性质的接口),因为这可能导致多个对象实例,与Singleton模式的初衷违背。   Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例。序列化对象可以把对象以不同的形式存储在内存中,如果将单例对象序列化,那么可能在内存中出现多个该对象。也不符合Singleton模式的要求。   Singleton模式只考虑到对象的创建(new的操作),没有考虑到对象的销毁管理,在.net平台上使用GC垃圾回收,一般不需要对对象的销毁进行特殊的管理。      Singleton模式是对类的限制,而不是对类进行改进扩展。   理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造函数进行任意的调用”。   可以对Singleton模式进行修改延伸,可以使它有少量的几个实例,这都是允许的。