天天看点

单例模式深入研究

原帖地址

前面章节主要涉及了一些预备的知识,从这一章起,我们将真正开始单例模式的研究

Singleton

首先看一个最常见的单例模式的实现,也是很多人常用的一种方式:

Singleton 设计模式的下列实现采用了 Design Patterns: Elements of Reusable Object-Oriented Software[Gamma95]

中所描述的解决方案,但对它进行了修改,以便利用 C#中可用的语言功能,如属性:

版本1:非线程安全的实现

// Bad Code ! Do not use!

public sealed class Singleton

{

    static Singleton instance=null;

    Singleton()

    {

    }

    public static Singleton Instance

    {

        get

        {

            if (instance==null)

            {

                instance = new Singleton();

            }

            return instance;

        }

    }

}

该实现主要有两个优点:

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

不想要的依赖性。

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

singleton。 

但是,这种实现的主要缺点是在多线程环境下它是不安全的。

如果执行过程的不同线程同时判断if(instance==null)语句,发现instance为null,那就可能会创建多个 Singleton对象实例。

解决此问题的方法有很多。

版本二:线程安全简洁版

public sealed class Singleton

{

    static Singleton instance=null;

    static readonly object padlock = new object();

    Singleton()

    {

    }

    public static Singleton Instance

    {

        get

        {

            lock (padlock)

            {

                if (instance==null)

                {

                    instance = new Singleton();

                }

                return instance;

            }

        }

    }

}

在第二个版本中,我们做到了线程安全。同时我们也实现了惰性加载机制。

这个版本的唯一不足之处是lock可能会对大量的并发访问的效率造成影响。但一般的应用中这样的效率损失可以忽略不计

如果访问的效率对我们性命攸关,那我们可以改进到第三个版本

版本三:双检锁(double-check locking)保证线程安全

// Bad Code ! Do not use!

public sealed class Singleton

{

    static Singleton instance=null;

    static readonly object padlock = new object();

    Singleton()

    {

    }

    public static Singleton Instance

    {

        get

        {

            if (instance==null)

            {   //@位置1   此位置可能有多个线程处于waitzhuangtai

                lock (padlock)

                {

                   //重复判断一次,即使在@位置1已经有多个线程涌入的情况下仍然能保证不会重复初始化Singleton

                    if (instance==null)

                    {

                        instance = new Singleton();

                    }

                }

            }

            return instance;

        }

    }

}

你可以对上面的代码进行一个思考,看上去双检锁机制似乎提高了访问的效率也保证了线程安全。但其实这样的写法在很多

平台和优化编译器上是错误的。

原因在于:instance = new Singleton();这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现

instance = new Singleton();

1. instance = 给新的实体分配内存

2. 调用Singleton的构造函数来初始化instance的成员变量((这里的初始化指的是初始化instance的实例成员,因为Singleton

的静态成员已经初始化过了)

现在想象一下有线程A和B在调用instance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是

instance已经不是null了

(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,

A还没有来得及执行步骤2来完成instance的初始化。

当然编译器也可以这样实现:

1. temp = 分配内存

2. 调用temp的构造函数

3. instance = temp

如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的。

要解决这个问题,我们可以利用 内存墙机制(MemoryBarrier)来解决这个问题,上面的代码改写如下:

版本三改正版1

public sealed class Singleton

{

    static Singleton instance=null;

    static readonly object padlock = new object();

    Singleton()

    {

    }

    public static Singleton Instance

    {

        get

        {

            if (instance==null)

            {

                lock (padlock)

                {

                    if (instance==null)

                    {

                        Singleton newVal = new Singleton();

                        System.Threading.Thread.MemoryBarrier();

                        instance = newVal;       

                    }

                }

            }

            return instance;

        }

    }

}

版本三改正版2

public sealed class Singleton

{

    static volatile Singleton instance=null;

    static readonly object padlock = new object();

    Singleton()

    {

    }

    public static Singleton Instance

    {

        get

        {

            if (instance==null)

            {

                lock (padlock)

                {

                    if (instance==null)

                    {

                        instance = new Singleton();

                    }

                }

            }

            return instance;

        }

    }

}

   以上谈到的一些如多线程安全、双险锁、内存墙、等概念涉及到很多编译原理和汇编的知识,由于个人能力有限,也

没法进一步加以详细阐释,大家有兴趣的话参考一下以下一些网址吧:

http://msdn.microsoft.com/msdnmag/issues/05/10/MemoryModels/default.aspx

 http://blogs.msdn.com/brada/archive/2004/05/12/130935.aspx

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

http://blog.joycode.com/demonfox/archive/2007/01/04/90894.aspx

http://www.cnblogs.com/dayouluo/archive/2005/12/25/304455.html

好了,到此为止,我们在第三个版本的两个改进版中同时兼顾了效率、线程安全和惰性加载。

但这两个改进版虽然从应用上没有问题,但似乎又涉及了过多的专业知识,把问题复杂化了,

让人顿生生疏之感。

既然如此,让我们再来看一个亲切一点的版本吧:

版本四:简单实现,非惰性加载

public sealed class Singleton

{

   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance

   {

      get

      {

         return instance;

      }

   }

}

这种实现简单,线程安全,缺陷主要是instance不是惰性加载的。准确的说是不一定是惰性加载的,因为我们无法得知instance

会在什么时候被初始化。若对我的描述有疑虑,请参考单例模式深入研究(一)