2010-06-23 45 views
6

我在生產中偶然發現了這個代碼,我認爲這可能會導致我們遇到問題。下面的代碼是從單例創建對象的堅實方式嗎?

internal static readonly MyObject Instance = new MyObject(); 

調用實例字段兩次返回具有相同哈希碼的兩個對象。這些對象有可能不同嗎?

我對CLI的瞭解說他們是相同的,因爲哈希碼是相同的。

請問誰能澄清?

回答

6

該字段只會初始化一次,因此您將始終獲得相同的對象。這非常安全。

當然,從多個線程使用靜態對象時必須小心。如果對象不是線程安全的,則應在從不同線程訪問它之前將其鎖定。

+1

鎖定它不是對象的責任,而是調用者的責任。但是,如果你使用了延遲初始化,那將是部分的。 – ErikHeemskerk 2010-06-23 08:12:25

+1

是的,這就是我所說的:「在訪問它之前應該鎖定它」。 – 2010-06-23 08:14:17

+1

正如在一個現在被刪除的答案中指出的那樣,如果用[ThreadStaticAttribute](http://msdn.microsoft.com/en-us/library/system.threadstaticattribute.aspx)標記'Instance' * *可能包含不同的對象):「ThreadStaticAttribute標記的靜態字段不在線程之間共享,每個正在執行的線程都有一個單獨的字段實例,並獨立地設置和獲取該字段的值,如果字段在不同線程上訪問,它將包含不同的價值「。 – 2010-06-23 08:17:08

1

它是靜態的,它屬於類,它是隻讀的,所以我不能在初始化後更改,所以是的,你會得到相同的對象。

-2

你認爲它們是相同的,因爲哈希碼相同是不正確的,GetHashCode()做你的對象的字段比較。

假設您沒有超載Object.Equals,你可以做一個簡單的比較相等,這是默認引用的比較:

這將輸出True,順便說一下,因爲你單身的實現正確的。 A static readonly字段保證只分配一次。但是,從語義上來說,只用get-accessor實現一個屬性並使用私有靜態字段作爲後備存儲會更加正確。

+0

'GetHashCode()'不一定會在對象的字段上進行比較。 – 2010-06-23 08:11:47

+1

該領域的方法適用於'結構',單身毫無意義。對於類,它是默認散列的對象引用。更好的檢查將是'ReferenceEquals(a,b)' – 2010-06-23 08:14:55

+0

@Marc Gravell:這將是一個更傻的檢查,但就像我說的,*假設你沒有重載Object.Equals *,'=='會照着做。 – ErikHeemskerk 2010-06-23 08:18:57

3

其他答案已評論岩石安全。這裏有更多關於你的一些參考散列代碼:

哈希碼是相同意味着兩個對象可能被認爲「平等」 - 不同的理念,以「相同的」。所有的哈希碼真的告訴你,如果兩個對象有不同的哈希碼,他們是肯定不是「等於」 - 因此暗示肯定不是「相同」。平等是通過覆蓋.Equals()方法來定義的,並且強加的合同是如果通過該方法認爲兩個對象相等,則它們必須從它們的方法返回相同的值。如果兩個變量的引用相同,則兩個變量「相同」 - 即它們指向內存中的同一個對象。

6

是的,它是安全的 - 最簡單的安全單例實現。

作爲比較所述哈希碼來推斷「它們是相同的對象」的另一點;因爲我們正在談論的引用類型在這裏(單是無意義的值類型),檢查的最佳方式,如果兩個引用指向同一個對象是:

bool isSame = ReferenceEqual(first, second); 

這是不依賴於GetHashCode()/Equals/==實現(它看參考本身)。

5

它是由CLR提供了保障,這將正常工作,即使在類由多個線程。

類似的還有當類型初始化發生在多線程系統,但更復雜的,存在的問題:這是在335了Ecma,分區II,節10.5.3.3指定。例如,在這些情況下,兩個單獨的線程可能開始嘗試訪問單獨的類型(A和B)的靜態變量,然後每個線程都必須等待另一個完成初始化。

的算法的粗略輪廓,以確保點1和2以上是如下:
1.類加載時(在初始化因此先前時間)存儲零或空入類型的所有靜態字段。
2.如果類型已初始化,則完成。
2.1。如果類型尚未初始化,請嘗試進行初始化鎖定。
2.2。如果成功,記錄此線程負責初始化類型並繼續執行步驟2.3。
2.2.1。如果不成功,看看這個線程或任何等待這個線程完成的線程是否已經擁有了 這個鎖。
2.2.2。如果是這樣,返回,因爲阻塞會造成死鎖。此線程現在將看到該類型的未完成初始化的 狀態,但不會出現死鎖。
2.2.3如果不是,則阻塞,直到類型被初始化然後返回。
2.3初始化基類類型,然後初始化由此類型實現的所有接口。
2.4執行此類型的類型初始化代碼。
2.5將類型標記爲已初始化,釋放初始化鎖,喚醒等待此類型的任何線程爲 初始化並返回。

需要明確的是,這是他們提出了一個CLR實現的算法,而不是你的代碼。

0

它會工作得很好,在這種情況下,但如果你有紀念實例[ThreadStatic]屬性然後直接初始化將無法正常工作,你將不得不使用別的東西,想偷懶的初始化,在這種情況下,你不如果使用單例的操作是「線程安全」的,那麼每個線程都是單例。

問候......

0

您可能會感興趣的事實,即初始化 的懶惰可能會有所不同。

喬恩斯基特建議,如果你願意添加一個空的靜態構造函數有關 實例時實際初始化。 爲了避免以錯誤的方式曝光,我爲您提供了關於他在單例模式中的 文章的鏈接。

你的問題涉及到他的文章中討論的第四種(和建議的)單例模式實現。

辛格爾頓:singleton implementation

裏面的文章,你發現在初始化的beforefieldinit和懶惰的鏈接的討論。