如果您正在多線程,那麼答案是肯定的。
小記:
顯然使用情況下,你不能把一個鎖,因爲它是空:-)
的鎖定模式是正常(這就是所謂的Double-checked locking):
if (instance == null)
{
lock (something)
{
if (instance == null)
{
instance = new Class();
}
}
}
如果你想(如果創建類不貴),你可以做:
if (instance == null)
{
Interlocked.CompareExchange(ref instance, new Class(), null);
// From here you are sure the instance field containts a "Class"
}
這段代碼的唯一問題是兩個線程可能會創建一個新的Class(),但只有一個能夠使用新Class()的引用來設置實例,所以另一個線程會創建一個無用的Class對象那將是GC。如果創建Class對象是便宜的(例如創建一個List<T>
)就沒關係。如果創建Class對象的代價很高(可能是因爲構造函數調用一個數據庫並做了一個大的查詢),那麼這個方法是一個禁忌。小記:格林奇已經到了,它已經宣佈所有這些多線程都不足夠「防萬無一失」。他是對的。他錯了。這就像是Schrödinger的貓! :-)上次我寫了一個多線程程序時,我只是讀了所有關於互聯網的文獻。而且我仍然犯了錯誤(但是,好吧,我正在嘗試編寫無鎖的MT代碼......這真是太重了)。因此,如果您使用的是.NET 4.0,那麼使用Lazy<T>
,如果不行的話...拿單聲道源文件找到Lazy定義的位置並複製它:-)(許可證非常寬容,但是讀取它!) 但是如果什麼你需要的是一個單身人士,你可以使用static Lazy<T>
或者如果你沒有.NET 4.0,你可以使用http://www.yoda.arachsys.com/csharp/singleton.html(第五個或第四個)的樣本。作者建議第四個。請注意,在那裏對它的懶惰有一些有趣的警告,但它們寫在http://www.yoda.arachsys.com/csharp/beforefieldinit.html)。請注意,你必須「寫作」。千萬不要想到它會偏離它所寫的內容。別。正如你可以看到通過評論閱讀,線程是一個硬性的論據。你可能是對的,同時你仍然可能是錯的。它就像揮發性物質(揮發性物質?是雙關語嗎?)化合物......非常非常有趣,非常非常危險。
另一個小提示:在.NET 1.1下它確實很粘。除非你完全知道你在做什麼,否則你不應該在1.1中的線程之間共享變量。在2.0版中,他們改變了內存模型(編譯器如何優化對內存的訪問),並且他們創建了一個「更安全」的內存模型。 .NET 1.1和更早版本的Java(包括1.4版本)都是坑陷阱。
要回答你的問題,一個簡單的技巧:當你在想「MT可以打破我的代碼嗎?」這樣做:想象一下Windows(操作系統)是一個懶惰的食人魔。有時候他讓一個線程停下半個小時,同時讓其他線程運行(技術上他可以做到這一點),而不是30分鐘(但沒有關於多長時間的真正規則),但是在毫秒內它可以並且如果處理器有一些工作被重載,並且有很多線程固定在特定的處理器上(固定意味着他們告訴操作系統他們只想在某些特定的處理器上運行,所以如果1000個線程固定在處理器1上,而處理器2只能執行1線程沒有固定,很明顯,處理器2上的線程將會更快!))。所以想象一下,兩個線程同時輸入你的代碼段,同時執行第一行(它們在兩個不同的處理器上,它們可以並行地執行),但是兩個線程中的一個停止並且具有等待30分鐘。同時會發生什麼?並且請注意,經常在代碼中間停止代碼! a = a + 1
是兩條指令!它是var temp = a + 1; a = temp;
如果你將這個技巧應用到示例代碼中,很容易看出:兩個線程都執行if (instance==null)
並傳遞,然後一個線程停止30分鐘,另一個線程初始化該對象,第一個線程恢復並初始化目的。兩個對象初始化。沒有良好的:-)
我會用一個簡單的例子解釋了.NET 1.1的問題:
class MyClass
{
public bool Initialized;
public MyClass()
{
Initialized = true;
}
}
MyClass instance = null;
public MyClass GetInstance()
{
if (instance == null)
{
lock (something)
{
if (instance == null)
{
instance = new Class();
}
}
}
return instance;
}
現在...這是代碼之前。該問題發生在instance = new Class()
行。讓我們分開它的各個部分:
- Class對象的空間由.NET分配。這個空間的引用被保存在某個地方。
- Class的構造函數被調用。 Initialized = true(該字段稱爲Initialized!)。
- 實例變量被設置爲我們之前保存的引用。
在.NET 2.0的較新「強大」內存模型中,這些操作將按此順序進行。但讓我們看看.NET 1.1會發生什麼:寫入可以重新排序!
- Class對象的空間由.NET分配。這個空間的引用被保存在某個地方。
- 實例變量被設置爲我們之前保存的引用。
- Class的構造函數被調用。 Initialized = true(該字段稱爲Initialized!)。
現在讓我們想象一下線程執行,這是由OS 30分鐘暫停2點後點3之前另一個線程可以訪問實例變量(請注意,在代碼中的第一個,如果沒有被保護通過一個鎖,所以它可以訪問它而不用等待第一個線程結束它的工作)並使用它。但類沒有真正初始化:他的構造函數沒有運行! Boooom!如果您在實例聲明(如此volatile MyClass instance = null;
)上使用了volatile
關鍵字,則不會發生這種情況,因爲編譯器無法重新排序寫入超出易失性字段上的寫入。所以他不能在點3之後重新排序點2,因爲在點3中它正在寫入一個易失性字段。但正如我寫的,這是.NET 1.1的一個問題。
現在。如果您想了解線程,請閱讀以下內容:http://www.albahari.com/threading/如果您想知道「幕後」會發生什麼,您可以閱讀http://msdn.microsoft.com/en-us/magazine/cc163715.aspx,但它很沉重。
可能值得[實施辛格爾頓Pattern in C#](http://csharpindepth.com/Articles/General/Singleton.aspx) –