代码改变世界

设计模式---单例模式

2014-11-09 20:40  周信达  阅读(226)  评论(0编辑  收藏  举报

前言

单例也是被嚼烂了的设计模式之一,但是这一模式在实际中确实使用非常广泛,今天,使用多个版本的单例模式实现,来讲一下实现单例需要注意的一些地方

版本1

使用静态字段,使用单例方法(属性)进行获取,第一次访问时进行初始化,以后直接返回

public sealed class Singleton
{
    // 私有构造函数,禁止外部访问
    private Singleton() { }

    // 静态实例字段
    private static Singleton _instance;

    // 静态实例方法
    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
                _instance = new Singleton();

            return _instance;
        }
    }
}

版本2

OK,单例方法的设计需要考虑多线程调用,所以线程同步是必须考虑的,否则可能就不是真的“单例”了

public sealed class Singleton
{    
    // 私有构造函数,禁止外部访问
    private Singleton() { }

    // 静态实例字段
    private static volatile Singleton _instance;
    private static readonly object syncRoot = new object();

    // 静态实例方法
    public static Singleton Instance
    {
        get
        {
            lock (syncRoot)
            {
                if (_instance == null)
                    _instance = new Singleton();

                return _instance;
            }
        }
    }
}

其实在实际使用中,这个版本已经OK了,但是线程同步还可以做一点小小的优化,于是

版本3

public sealed class Singleton
{
    // 私有构造函数,禁止外部访问
    private Singleton() { }

    // 静态实例字段
    private static volatile Singleton _instance;
    private static readonly object syncRoot = new object();

    // 静态实例方法
    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (syncRoot)
                {
                    if (_instance == null)
                        _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
}

OK,现在这个版本已经完美了,可以使用了,而且线程同步进行了优化,不用每一次调用都进行lock操作,但是等等,很多人还有另一种风格,就是在静态字段中提供内联初始化或者默认初始化,然后让属性或单例方法直接返回该静态字段。因为使用静态字段的初始化语法,其实可以保证线程安全(CLR是这么处理的),所以不用我们自己去编写线程同步代码

版本4

public sealed class Singleton
{
    // 私有构造函数,禁止外部访问
    private Singleton()
    {
        Console.WriteLine("Singleton Constructed");
    }

    // 静态实例字段
    private static readonly Singleton _instance = new Singleton();

    // 静态实例方法
    public static Singleton Instance
    {
        get
        {
            return _instance;
        }
    }
}

OK,现在不用在获取实例的方法(属性)中写构造表达式了,更不用自己写线程同步的代码了,但是现在有一个问题,那就是字段的内联初始化的初始化时间是提前的,而且是不确定的,因为没有提供默认静态构造函数的话,静态字段的内联初始化会生成beforeInit标记,字段的初始化会在使用类型以前随机寻找一个时间来调用,这可以适度优化

版本5

public sealed class Singleton
{
    // 私有构造函数,禁止外部访问
    private Singleton()
    {
        Console.WriteLine("Singleton Constructed");
    }

    // 默认静态构造函数
    static Singleton() { }

    // 静态实例字段
    private static readonly Singleton _instance = new Singleton();

    // 静态实例方法
    public static Singleton Instance
    {
        get
        {
            return _instance;
        }
    }
}

看到区别了么?现在只是加了一个静态构造函数,为了防止内联过早的进行调用,现在不会生出fieldBeforeInit信息,所以会到调用之前才进行初始化。但是关于调用的时机,是不是依然可以优化,优化到真正调用时才进行初始化呢?好了,请看下一个版本

版本6

public class Singleton
{
    // 私有构造函数,禁止外部访问
    private Singleton()
    {
        Console.WriteLine("Singleton Constructed");
    }

    public static Singleton Instance
    {
        get { return NestedObject.NestedInstance; }
    }

    private class NestedObject
    {
        static NestedObject() { }

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

这个版本使用内嵌的私有类作为一个容器来维持单例对象,使用静态初始化的方式并且支持延迟初始化,非常聪明。那么是否到此为止呢?其实对于静态初始化的延迟执行,.NET提供了非常方便的Lazy类,我们何不拿来一用,这样我们就不用自己编写内部类了

版本7

public class Singleton
{
    private static Lazy<Singleton> _instance
        = new Lazy<Singleton>(() => new Singleton());

    // 私有构造函数,禁止外部访问
    private Singleton()
    {
        Console.WriteLine("Singleton Constructed");
    }

    public static Singleton Instance
    {
        get { return _instance.Value; }
    }
}

总结

大家对单例模式的说法一向都是简约不简单,所以此模式虽然已被嚼烂,但它依然是作为程序员的必修课之一,这里面考察的主要是

  • 类的修饰符,一般建议使用 sealed,密封类,不允许继承
  • 类的构造函数,一般建议private,不允许外部访问
  • 类的执行时机,根据实际需求判断,是否饿汉式初始化或者惰性初始化
  • 类的初始化线程安全的考虑,必须考虑多线程访问时,构造仅执行一次

实践中,可用的方式包括,版本3(单例方法初始化,加双检查锁定,延迟初始化)、版本4、5(静态初始化,非延迟初始化)、版本6、7(静态初始化,延迟实例化),其实都是可以使用的,具体选择哪个没有标准的,根据实际应用场景来选择吧