在MultiThreaded方案中正确锁定List ?

好吧,我无法正确理解multithreading场景。 很抱歉再次提出类似的问题,我只是在互联网上看到许多不同的“事实”。

public static class MyClass { private static List _myList = new List; private static bool _record; public static void StartRecording() { _myList.Clear(); _record = true; } public static IEnumerable StopRecording() { _record = false; // Return a Read-Only copy of the list data var result = new List(_myList).AsReadOnly(); _myList.Clear(); return result; } public static void DoSomething() { if(_record) _myList.Add("Test"); // More, but unrelated actions } } 

我们的想法是,如果激活录制,则对DoSomething()的调用将记录在内部List中,并在调用StopRecording()时返回。

我的规格是这样的:

  • StartRecording不被视为线程安全。 用户应该在没有其他Thread调用DoSomething()时调用它。 但如果它能以某种方式存在,那就太好了。
  • StopRecording也不是正式的线程安全的。 再说一次,如果它可能会很好,但这不是一个要求。
  • DoSomething必须是线程安全的

通常的方式似乎是:

  public static void DoSomething() { object _lock = new object(); lock(_lock){ if(_record) _myList.Add("Test"); } // More, but unrelated actions } 

或者,声明一个静态变量:

  private static object _lock; public static void DoSomething() { lock(_lock){ if(_record) _myList.Add("Test"); } // More, but unrelated actions } 

但是, 这个答案说这并不妨碍其他代码访问它。

所以我想知道

  • 我如何正确锁定列表?
  • 我应该在我的函数中创建锁对象还是作为静态类变量?
  • 我是否可以在锁定块中包含Start和StopRecording的function?
  • StopRecording()做两件事:将布尔变量设置为false(以防止DoSomething()添加更多东西)然后复制列表以将数据的副本返回给调用者。 我假设_record = false; 是primefaces的,会立即生效吗? 所以通常我根本不用担心multithreading,除非其他一些Thread再次调用StartRecording()?

在一天结束时,我正在寻找一种方式表达“好吧,这个列表现在是我的,所有其他线程必须等到我完成它”。

我将锁定_myList本身,因为它是私有的,但使用单独的变量更常见。 要改进几点:

 public static class MyClass { private static List _myList = new List; private static bool _record; public static void StartRecording() { lock(_myList) // lock on the list { _myList.Clear(); _record = true; } } public static IEnumerable StopRecording() { lock(_myList) { _record = false; // Return a Read-Only copy of the list data var result = new List(_myList).AsReadOnly(); _myList.Clear(); return result; } } public static void DoSomething() { lock(_myList) { if(_record) _myList.Add("Test"); } // More, but unrelated actions } } 

请注意,此代码使用lock(_myList)来同步对_myList _record的访问。 并且您需要同步这两个上的所有操作。

并且为了同意这里的其他答案, lock(_myList)对_myList没有任何作用,它只使用_myList作为标记(可能是HashSet中的键)。 通过使用相同的令牌询问权限,所有方法都必须公平。 另一个线程上的方法仍然可以使用_myList而不首先锁定,但结果不可预测。

我们可以使用任何令牌,所以我们经常创建一个令牌:

 private static object _listLock = new object(); 

然后在任何地方使用lock(_listLock)而不是lock(_myList)

如果myList已经公开,那么这种技术是可取的,如果你重新创建myList而不是调用Clear(),那将是绝对必要的。

DoSomething()创建一个新锁肯定是错误的 – 这将是毫无意义的,因为每次调用DoSomething()都会使用不同的锁。 您应该使用第二种forms,但使用初始化程序:

 private static object _lock = new object(); 

确实,锁定不会阻止任何其他内容访问您的列表,但除非您直接公开列表,否则无关紧要:无论如何都不会访问列表。

是的,您可以以相同的方式将Start / StopRecording包装在锁中。

是的,设置布尔变量是primefaces的,但这不会使它成为线程安全的。 如果你只访问同一个锁中的变量,那么你在primefaces性波动性方面都很好。 否则,您可能会看到“陈旧”值 – 例如,您在一个线程中将值设置为true ,而另一个线程在读取时可以使用缓存值。

有几种方法可以锁定列表。 您可以直接锁定_myList, 前提是_myList永远不会更改为引用新列表。

 lock (_myList) { // do something with the list... } 

您可以专门为此目的创建锁定对象。

 private static object _syncLock = new object(); lock (_syncLock) { // do something with the list... } 

如果静态集合实现System.Collections.ICollection接口(List(T)),则还可以使用SyncRoot属性进行同步。

 lock (((ICollection)_myList).SyncRoot) { // do something with the list... } 

要理解的要点是你想要一个只有一个对象用作你的锁定sentinal,这就是为什么在DoSomething()函数中创建锁定的sentinal不起作用的原因。 正如Jon所说,每个调用DoSomething()的线程都会获得自己的对象,因此该对象的锁定每次都会成功并立即授予对列表的访问权限。 通过使锁定对象静态(通过列表本身,专用锁定对象或ICollection.SyncRoot属性),它将在所有线程之间共享,并且可以有效地序列化对列表的访问。

第一种方法是错误的 ,因为每个调用者将锁定不同的对象。 你可以锁定列表。

 lock(_myList) { _myList.Add(...) } 

你可能误解了这个答案 ,实际上说的是他们的lock语句实际上没有锁定有问题的对象被修改,而是阻止任何其他代码使用该对象作为锁定源执行。

这实际上意味着当您使用与锁定对象相同的实例时,锁定块内的代码不应该被执行。

实质上,你并没有真正试图“锁定”你的列表,你试图有一个可以作为参考点的公共实例,当你想要修改你的列表时,当你正在使用它或你想要“锁定”时防止执行可能会修改列表的其他代码。