C#中的单例模式

通常的,设计模式中用到单例模式,代码一般会以下:编程

public sealed class Singleton
{
    private static Singleton instance=null;

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

代码比较简单,用到一个公有的静态属性和一个私有的静态字段。而且把构造函数设为私有,防止该类被实例化。c#

但上述代码在多线程状况下并不可靠。有一种状况下。2个线程在get的时候,都检测到instance==null,所以各自建立了一个Singleton对象,破坏了单例的原则。设计模式

所以改进后的代码就是加锁。安全

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

加锁以后,多线程的操做也变的同步了,同一时间只能有得到锁的那个线程才能建立对象。这保证了对象的惟一,可是这个会损耗性能。多线程

由于每次get的时候,都会加锁,所以能够把代码修改一下,若是对象已经存在了,就不须要加锁来建立对象。代码修改以下:ide

public sealed class Singleton
{
    private static volatile Singleton instance = null;
    private 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;
        }
    }
}

仔细看一下,就会发现,上述代码中,有2次检测instance == null,所以也成为双重检测。注意,因为Java中内存模型的问题,上述双重检测代码,对Java不必定有效。函数

在C#代码中,考虑下面的场景,有2个线程A,B性能

1.A线程进入getspa

2.A线程检测到instance==null线程

3.A线程得到锁

4.A线程实例化一个对象

5.B线程进入get

6.B线程检测是否instance==null

问题出如今第4,5,6步骤。

当第4步执行的时候,颇有可能出现这种状况,CLR为Singleton对象在托管堆上分配了空间,而且让instance指向了这个空间,而后再去调用Singleton的构造函数。而漏洞就在这里,线程B可能未等到线程A运行完Singleton的构造函数,就进入get检测instance!=null,认为对象不是null,而后就直接返回instance,而此时,instance指向的值还在初始化呢,这就可能致使线程B获得的对象是没彻底初始化成功的,可能引发代码错误。固然,这种错误的可能性很是少见,但仍是会有必定的几率。

所以,上述代码中instance变量加了一个关键字volatile,加它的做用,是为了访问这个instance的时候,确保instance分配了空间而且初始化完成了(volatile确保该字段在任什么时候间呈现的都是最新的值)。

若是不使用volatile关键字,也能够将instance = new Singleton();语句替换成Interlocked.Exchange(ref instance,new Singleton())。

在C#中,对于实现单例模式,更为推崇的方法是使用静态变量初始化。

有些设计模式的书中,避免使用静态初始化的缘由之一是C++ 规范在静态变量的初始化顺序方面留下了一些多义性。幸运的是,.NET Framework 经过其变量初始化处理方法解决了这种多义性:

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();
   
   private Singleton(){}

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

CLR保证了静态字段初始化操做老是线程安全的,不管多少线程同时访问该类,类中的静态字段只可能被初始化一次(每一个应用程序域)。

这是由于,类中的静态字段的初始化,是由类的静态构造函数完成的,C#编译器检测到类中有静态字段后,会为该类生产一个静态构造函数(能够从IL代码中看到.cctor方法),也就是说下面代码是等价的:

class SomeType{
    Static int x = 5;
    }

等价于

class SomeType
{
    Static int x;
    Static SomeType()
    {
        x = 5;
    }
}


所以上述的instance只会初始化一次。保证了单例。这种方法使用了CLR的特性,对于其余语言并不保证,是.NET平台上推崇的一种实现singleton的方式。

上述实现方法有个不足在于不能延迟加载对象,若是Singleton中还有其余静态字段,引用该静态字段的时候,会致使Singleton被建立了。所以,能够在该类中再嵌套一个类来实现延迟加载。以下:

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }
        
    private class Nested
    {
        internal static readonly Singleton instance = new Singleton();
    }
}

上述代码,只有当访问Instance属性的时候,才会触发Nested类的静态字段,从而初始化一个Singleton对象,所以实现了延迟加载,可是设计比较复杂,不推荐使用。

在.NET4.0中,还有一种更优雅的方法实现延迟加载,即便用Lazy<T>对象。

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

能够参考以前总结的文章http://cnn237111.blog.51cto.com/2359144/1213187


参考:

http://csharpindepth.com/Articles/General/Singleton.aspx#unsafe

http://msdn.microsoft.com/en-us/library/ff650316.aspx

http://mcwilling.blog.163.com/blog/static/1950971712013357359564/

NET4.0面向对象编程漫谈基础篇.金旭亮

相关文章
相关标签/搜索