2014-11-23 25 views
3

我有下面的代碼,其中「_ht」是一個代表緩存的哈希表,「_isLoaded」代表它是否加載。是否可以使用Hashtable作爲鎖對象?

我們的系統有許多進程訪問「_ht」對象,並且我需要他們等待它沒有加載。

使用「_ht」作爲鎖對象是否錯誤?我應該在這種情況下使用專用的對象類型類成員嗎?

重要的是要提到,這個類是SINGLETON。

private Hashtable _ht = new Hashtable(); 
private bool _isLoaded = false; 

internal Hashtable GetHT() 
     { 
      if (_isLoaded == false) 
      { 
       lock (_ht) 
       { 
        if (_isLoaded == false) 
        { 
         LoadHt(_ht); 
        } 
       } 
      } 

      return _ht; 
     } 
+0

如果你想讓我花費我的答案,爲什麼要使用鎖對象說,所以我認爲這是非常明顯的,如果不是,那麼你應該考慮它。反正lemme知道 – 2014-11-23 08:37:47

+0

@ohadinho很多人說_usually_使用了一個單獨的'object',但直到你鎖定的對象是'private',而不是直接使用你的'Hashtable'沒有任何錯誤(記住你正在鎖定一個實例,而不是一個類,而且沒有其他人知道該對象鎖定它,所以它不是死鎖的來源)。這就是說我會**這個代碼全部放在一起,我會用'懶惰'。 – 2014-11-23 08:44:08

+0

@AdrianoRepetti:請注意,僅僅因爲_field_被聲明爲「private」,並不意味着該對象本身是私有的。實際上,在這個例子中,'Hashtable'對象實際上是通過'GetHT()'方法公開的(儘管只是'內部',但仍然在擁有類之外)。 – 2014-11-23 08:50:57

回答

10

你當然可以鎖定Hashtable對象,就像你可以在lock語句中使用任何引用類型實例的.NET。然而,它通常被認爲是一種劣質的方法,主要是因爲當代碼的其他部分有一個或多個鎖對象可用時,他們很難跟蹤代碼如何使用鎖定,他們也可能使用它來鎖定再次,不可思議,但你會驚訝於人們寫的代碼)。

對於通常的鎖定,單獨的鎖定對象是最好的。我會注意到在你的代碼示例中,_ht應該是readonly,並且如果你添加一個單獨的鎖定對象(例如lockObj),它也應該是隻讀的。

也就是說,單例情景不應該以這種方式實現。相反,您應該使用CLR自己的靜態初始化,或Lazy<T>類:

private static readonly Hashtable _ht = InitializeTable(); 

internal static Hashtable GetHT() { return _ht; } 

private static Hashtable InitializeTable() 
{ 
    Hashtable table = new Hashtable(); 

    LoadHt(table); 

    return table; 
} 

或者:

private static readonly Lazy<Hashtable> _ht = new Lazy<Hashtable>(() => InitializeTable()); 

internal static Hashtable GetHT() { return _ht.Value; } 

private static Hashtable InitializeTable() 
{ 
    Hashtable table = new Hashtable(); 

    LoadHt(table); 

    return table; 
} 

時,你有可能被訪問的類型中的其他成員,後者是有用的,但是你想確保散列表的初始化儘可能延遲(例如,如果可能的話,沒有代碼實際上可以訪問它,所以你可以避免初始化它)。

(我將所有內容都更改爲static,因爲您將您的場景描述爲單例,在這種情況下,只有static成員對代碼示例有意義)。

最後我會注意到Hashtable這個類已經過時了。作爲非泛型類,您應該認真考慮升級代碼以使用現在十年的泛型類型。 Dictionary<TKey, TValue>類是最直接的替代品,但人們有時使用Hashtable作爲一個簡單集合,對此,數據結構更合適。

+0

只有例外的答案是「就像你可以在.NET中使用任何引用類型實例」一樣 - 除了字符串。您不能將字符串有效地用作鎖對象,因爲它們是不可變的,所以它們的引用在具有相同字符串值的所有其他代碼之間共享。 – PhillipH 2014-11-23 08:49:26

+1

@PhillipH:對不起,你的陳述是不正確的。首先,「字符串」是不可變的事實絕不意味着所有相同的字符串實際上是相同的對象(默認情況下,只有字符串文字被實現)。其次,即使它確實暗示了這一點,但並不排除在'lock'語句中使用'string'引用。這可能是非常不明智的,但它肯定是可能的。 – 2014-11-23 08:54:09

+0

非常感謝您的解決方案。 我有一個問題: 如果10個線程試圖執行「GetHT()」和Hashtable沒有加載。 只有一個線程會執行InitializeTable(),因爲它是靜態的嗎? – ohadinho 2014-11-23 09:13:53

2

如果你想要第一個線程來這裏啓動它沒關係。但通常你使用一個鎖對象。

private Hashtable _ht = new Hashtable(); 
private bool _isLoaded = false; 
private object lockObj = new object(); 

internal Hashtable GetHT() 
{ 
    if (_isLoaded == false) 
    { 
     lock (lockObj) 
     { 
      if (_isLoaded == false) 
      { 
       LoadHt(_ht); 
      } 
     } 
    } 

    return _ht; 
} 
+0

通常應該避免雙重檢查鎖定,除非沒有其他原因,否則人們很容易錯誤地實現它(如此處所示)。在.NET中也是不必要的,因爲.NET提供至少兩種其他高級實現選擇。請參閱http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Microsoft_.NET_.28Visual_Basic.2C_C.23.29,http://stackoverflow.com/a/394932/3538012和http://csharpindepth.com/文章/ General/Singleton.aspx關於該主題的一些額外討論。 – 2014-11-23 08:46:05

+0

這不是單例模式,但是這種雙重檢查鎖定被打破。如果'_ht'在'LoadHt'方法中被實例化,那麼處理器可以對'_ht'和'_isLoaded'重新排序,從而返回'null'。請參閱http://joeduffyblog.com/2006/01/26/broken-variants-on-doublechecked-locking/ – 2014-11-23 08:50:32

+0

@SriramSakthivel:實際上它比Duffy博客文章中的示例更糟糕。在這個例子中,'_isLoaded'變量從未被賦值,所以它甚至沒有機會偶然發現Duffy談論的問題。 :)但是,沒有易失性讀取也是一個問題。這就是爲什麼我總是強烈反對試圖實施自己的雙重鎖定。相對較少的程序員是正確的,在.NET中有很多簡單的選擇可供使用。 – 2014-11-23 09:02:39

1

對象本身未鎖定(保護)開始。 lock關鍵字中使用的引用用於標記或標記代碼中不應與使用相同對象引用的任何其他(或相同)代碼段同時運行的代碼段。它實際上並不影響對象本身。 所以回答是是的你可以在鎖定語句中使用你現有的HashTable實例。

Best practice雖然是定義一個私有對象來鎖定,或者私有靜態對象變量來保護所有實例的公共數據。

private Object thisLock = new Object(); 

編輯感謝@PeterDuniho您指出

+0

感謝您修復關於「新對象」的聲明。我還建議你澄清「使用相同對象引用的代碼段」。正如你可能知道的那樣,它只保護在'lock'語句中使用相同對象引用的代碼段。任何使用該對象而不使用「lock」語句的代碼將保持不同步。我想你可以簡單地把它寫成「......在'lock'語句中使用相同的對象引用」,這將確保不會有任何誤解。 – 2014-11-23 08:58:28

相關問題