2014-11-23 80 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

相关问题