2015-01-08 50 views
0

我使用一個單獨的類之間共享:靜態實例不說,假設有一個單一的靜態實例如下線程

private static ISingletonClass _instance = null; 

    public static ISingletonClass GetInstance(string id = null) 
    { 
     if (_instance == null) 
     { 
      if (id != null) 
      { 
       _instance = new SingletonClass(id); 
      } 
      else 
      { 
       throw new NullReferenceException("id is missing!"); 
      } 
     } 

     if (id != null && _instance.Id != id) 
     { 
      _instance = new SingletonClass(id); // changing instance 
     } 

     return _instance; 
    } 

類中的所有其他代碼是不是靜態的(包括ID屬性)。 早在跑的時候仍然有一個單一的,線程我初始化單身一些ID,像這樣:

SingletonClass.GetInstance(<some_not_null_id>); 

的_instance設置爲不空(選中它)。 後來我創建了幾個線程來完成一些任務,除此之外,還需要從SingletonClass中讀取信息(不寫)。 根據我發現的任何文檔和StackOverflow中的答案,所有線程都可以使用同一個實例(我沒有使用[ThreadStatic]或任何其他類似的機制)。

但是,如果在線程內部沒有參數的情況下嘗試GetInstance(),我會得到NullException(_instance成員爲空)。

我正在使用.NET版本4.5,並使用VS2012。

任何想法?

+2

注意:'NullReferenceException'是一個保留的異常,因此你不應該拋出它。 –

+2

你會得到你的nullexception,那個「id已失蹤!」信息? – weston

+3

您可以使用['ArgumentNullException'](http://msdn.microsoft.com/zh-cn/library/system.argumentnullexception%28v=vs.110%29.aspx) – weston

回答

2

首先,你對拋出的異常的假設是不正確的:

NullException(該_instance成員爲NULL)。

從你的GetInstance()方法中拋出的唯一的NullReferenceException就是你自己拋出的方法。假設您沒有任何其他代碼將_instance值重置爲null,則該方法解除引用_instance值的唯一地方是在未確保將_instance初始化爲某個非空值的情況下無法達到的語句。

至於更廣泛的問題,恕我直言,最大的問題是,你有一個定義不明確的問題,以及一個破壞的實現。

即使忽略「singleton」(它不是真的是單身人士,但爲了參數讓我們現在稱之爲)的問題是否改變,初始化不是線程安全的。您有以下潛在的競爭(假設單個CPU內核進行了說明簡單):

Thread 1     Thread 2 
--------     -------- 
call GetInstance() 
if (_instance == null) 
--> preempted <-- 
          call GetInstance() 
          if (_instance == null) 
          ... 
          _instance = new SingletonClass(id); 
          ... 
          return _instance; 
          --> preempted <-- 
if (_instance == null) 
... 
_instance = new SingletonClass(id); 
... 
return _instance; 

正如你可以在上面的例子中看到,現在的代碼寫入的方式,每個線程可以獨立嘗試檢索該實例將看到當前值爲null,並且會創建一個新的實例對象以返回。

對於一個真正的單身人士,爲了實現這一目標的最佳方法是使用Lazy<T>類:

private static readonly Lazy<SingletonClass> _instance = 
    new Lazy<SingletonClass>(() => new SingletonClass()); 

public static SingletonClass Instance { get { return _instance.Value; } } 

在這種情況下,Lazy<T>類處理所有的初始化工作,包括確保它在thread-完成安全的方式。

在你的情況下,如果你沒有一個真正的單身人士,上述不起作用。 Lazy<T>模式僅適用於初始化一次某事物,但您希望能夠即時更改它。鑑於這種情況,你需要更多的東西是這樣的:

private static ISingletonClass _instance = null; 
private static readonly object _lock = new object(); 

public static ISingletonClass GetInstance(string id = null) 
{ 
    lock (_object) 
    { 
     if (_instance == null || (id != null && _instance.Id != id)) 
     { 
      if (id == null) 
      { 
       throw new ArgumentNullException("id"); 
      } 

      _instance = new SingletonClass(id); 
     } 

     return _instance; 
    } 
} 

以上將確保對線程同時初始化場。它們只能競爭到鎖,然後一個線程保證是唯一初始化對象的線程,假設每個線程都通過相同的值id

也就是說,這隻能解決代碼中的基本線程安全問題。有一些更大的問題。

首先,如果當一個線程檢索當前實例時,您希望代碼執行什麼操作,那麼在第一個線程完成使用它檢索的實例之前,其他某個線程會更改當前實例?我並不是說這種情況本質上是破裂的,但它至少非常脆弱,你絕對需要考慮這種情況,並自己決定在這種情況下應該採取什麼正確的行爲。其次,這是單身模式的一個非常脆弱的突變。一個真正的單身人士將擁有一個單一的對象,在該過程的整個生命週期中只分配一次。這確保了設計的簡單性和行爲的可預測性。

你的實現必然會讓你很難理解代碼在任何給定點上正在做什麼。在某些開發人員的日子裏,無論是你的還是其他人的,當出現一些錯誤並且試圖追蹤錯誤的真正原因時,實際上可以保證爲開發者添加大量時間。

,這個類的實例被捆綁爲一個字符串ID表明,一個更好的方法是保持Dictionary<string, SingletonClass>對象,要求所有求助者總是指定的ID,並使用該ID檢索(可能是事實當然懶惰初始化)當前線程需要的對象。

我強烈推薦一個不同的設計。但至少,如果您決定必須遵循這個方向,請確保您已經考慮了線程事件的所有各種組合,並且不僅決定了每個給定場景中的正確行爲,還添加了代碼以確保假設限制。

+0

謝謝您的全面回覆。我需要做一些說明:_instance成員爲空。這是事實。我調試了它。你所描述的場景在這種情況下是不可能的,因爲就像我最初寫的那樣,在創建線程之前,實例已成功設置(調試它)。線程只能從實例中讀取,而不是寫入或更改它。我確實需要在代碼中添加一些線程安全機制,這種藝術是真實的。 – Ziv

+0

'_instance'成員爲'null'的事實並不意味着在使用'_instance'值(即對其進行解引用)時出現'NullReferenceException'。如果你的意思是'null'的值導致你自己的代碼顯式地拋出'NullReferenceException',那麼我誤解了......我的觀點是,它是你自己的'拋出新的NullReferenceException ...',這是導致異常,不是_instance_值的實際使用。 –

0

我相信你想要一個真正的單身人士,你可以更新一個值。這個更新需要是線程安全的。創作應該是一個與獲得相關的獨立方法。

private static readonly MyContainerClass _instance = new MyContainerClass(); //true singleton 

private sealed class MyContainerClass //this is the singleton 
{ 
    private ISingletonClass _value = null; 

    public ISingletonClass Value //threadsafe access to your object 
    { 
     get { lock(this){ return _value; } } 
    } 

    public ISingletonClass CreateOrUpdateValue(string id) //threadsafe updating of your object 
    { 
     if (id==null) throw new ArgumentNullException("id is missing!"); 
     lock(this) 
     { 
     var instance = _instance.Value; 

     if (instance == null || instance.Id != id) 
      _instance.Value = new SingletonClass(id); 

     return _instance.Value; 
     } 
    } 
} 

public static void CreateOrUpdateInstance(string id) 
{ 
    _instance.CreateOrUpdateValue(id); 
} 

public static ISingletonClass GetInstance() 
{ 
    var instance = _instance.Value; 

    if (instance == null) 
     throw new Exception("Instance has not been created"); 

    return _instance; 
} 

// this is like your original method if you really want it 
public static ISingletonClass GetInstance(string id) 
{ 
    return _instance.CreateOrUpdateValue(id); 
} 
+0

感謝您的意見,一般來說 - 您是對的。在這種情況下,就像我寫的那樣,創建實例之前(連續)創建更多線程。線程不會試圖替換實例,只是爲了得到它(不提供id)。仍然 - 它沒有解釋問題 - 爲什麼不在線程之間共享靜態成員? – Ziv

+0

也許是因爲你的代碼不是線程安全的。你沒有鎖。 – weston