CICN

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

单例模式估计是所有设计模式中最简单的一种了,也是应用得最多的一种。

通常情况下,之所以要使用单例,是因为创建一个实例需要耗费大量的资源,因此,通常单例会采用延迟创建的模式。即在访问单例实例时才进行创建:

    private static Singleton _instance;
    public Singleton Instance
    {
        get { return _instance ?? (_instance = new Singleton()); }
    }

但这种延迟创建,在多线程环境下的是不安全的,在多个线程同时执行到 ?? 表达式的时候,他们可能都判断 _instance 为 null, 认为实例未创建,从而各自创建一个实例。

SO,一种解决办法是加锁,并且进行 Double Check 双重检查:

    private volatile static Singleton _instance;
    private static object _syncLock = new object();
    public Singleton Instance
    {
        get 
        {
            if (_instance == null )
            {
                lock (_syncLock)
                {
                    if (_instance == null) _instance = new Singleton(); 
                }
            }

            return _instance;
        }
    }

这是 MS 的官方写法,这也是一个标准的 Double Check 写法,也就是在加锁前后都检查 _instance == null。这个创建的过程逻辑已经复杂到足够让一些人蛋疼了。

但是理论上来说,Double Check 这种写法也是不安全的。MS 这样写是否线程安全,和 Java 的类似代码一样,还是要取决于编译器如何实现对象创建的逻辑,假如编译创建对象的逻辑为:

  1. temp = 为要创建的对象分配内存
  2. 调用对象的构造函数
  3. instance = temp

那么应该没什么问题。但是编译器的逻辑是这样:

  1. instance = 为要创建的对象分配内存
  2. 调用对象的构造函数

那么这个 Double Check 同样是线程不安全的,其中一个线程执行完第一步时 _instance 已经不是 null 了,但是第二步还未执行,对象还未初始化,其他线程却可通过 _instance 开始调用对象的某个方法,这时候,由于对象的所对应的内存还未完成初始化,这可能导致一个非常严重的指针泄露问题。

SO,公布最后答案:最安全并且最简洁的单例延迟创建模式应该如下所示,这是 Google公司的工程师 Bob Lee 创造的写法,对于 C# 和 Java 都适用。简单而高效,确实介于牛A和牛C之间。

    public class Singleton
    {
        private static class Holder
        {
            public static readonly Singleton _instance = new Singleton();
        }

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

 

posted on 2013-08-21 12:19  CICN  阅读(145)  评论(0编辑  收藏  举报