首先,你對拋出的異常的假設是不正確的:
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檢索(可能是事實當然懶惰初始化)當前線程需要的對象。
我強烈推薦一個不同的設計。但至少,如果您決定必須遵循這個方向,請確保您已經考慮了線程事件的所有各種組合,並且不僅決定了每個給定場景中的正確行爲,還添加了代碼以確保假設限制。
注意:'NullReferenceException'是一個保留的異常,因此你不應該拋出它。 –
你會得到你的nullexception,那個「id已失蹤!」信息? – weston
您可以使用['ArgumentNullException'](http://msdn.microsoft.com/zh-cn/library/system.argumentnullexception%28v=vs.110%29.aspx) – weston