LUKIYA'S NEVERLAND

春去秋来,花谢花开。


前面章节主要涉及了一些预备的知识,从这一章起,我们将真正开始单例模式的研究
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
会在什么时候被初始化。若对我的描述有疑虑,请参考单例模式深入研究(一)。