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面向對象編程漫談基礎篇.金旭亮

相關文章
相關標籤/搜索