这个锁用法线程安全吗?

我知道使用lock(this)或任何共享对象是错误的。

我想知道这种用法是否合适?

 public class A { private readonly object locker = new object(); private List myList; public A() { myList = new List() } private void MethodeA() { lock(locker) { myList.Add(10); } } public void MethodeB() { CallToMethodInOtherClass(myList); } } public class OtherClass { private readonly object locker = new object(); public CallToMethodInOtherClass(List list) { lock(locker) { int i = list.Count; } } } 

这个线程安全吗? 在OtherClass我们使用私有对象锁定,因此如果class A锁具有其私有锁,那么列表是否仍会在OtherClass的锁定块中发生OtherClass

不,这不是线程安全的。 可以在“相同”时间执行添加和计数。 您有两个不同的锁定对象。

传递列表时始终锁定自己的锁定对象:

  public void MethodeB() { lock(locker) { CallToMethodInOtherClass(myList); } } 

不,这不是线程安全的。 A.MethodeAOtherClass.CallToMethodInOtherClass锁定在不同的对象上,因此它们不是互斥的。 如果您需要保护对列表的访问权限,请不要将其传递给外部代码,将其保密。

不,这不是线程安全的。 为了使线程安全,您可以使用锁定static对象,因为它们在线程之间共享,这可能会导致代码死锁,但可以通过维护正确的锁定顺序来处理它。 存在与lock相关的性能成本,因此明智地使用它。

希望这可以帮助

不,这不是线程安全的。

你的2种方法锁定在2个不同的对象上,它们不会互相锁定。

因为CallToMethodInOtherClass()只检索Count的值,所以不会出现可怕的错误。 但是它周围的lock()是没用的,也是误导性的。

如果该方法会在列表中进行更改,那么您将遇到一个令人讨厌的问题。 要解决它,请更改MethodeB:

  public void MethodeB() { lock(locker) // same instance as MethodA is using { CallToMethodInOtherClass(myList); } } 

不,他们必须锁定同一个对象。 使用您的代码,它们可以锁定不同的,并且每个调用都可以同时执行。

要使代码线程安全,请在MethodeB中锁定一个锁或将列表本身用作锁定对象。

它实际上是线程安全的(纯粹作为Count上的实现细节问题),但是:

  1. 线程安全的代码片段不是线程安全的应用程序。 您可以将不同的线程安全操作组合到非线程安全操作中。 实际上,许多非线程安全的代码可以分解成更小的部分,所有部分都是自己的线程安全的。

  2. 由于您希望的原因,它不是线程安全的,这意味着进一步扩展它不会是线程安全的。

这段代码是线程安全的:

 public void CallToMethodInOtherClass(List list) { //note we've no locks! int i = list.Count; //do something with i but don't touch list again. } 

用任何列表调用它,它将根据该列表的状态给出一个值,无论其他线程是什么。 它不会破坏list 。 它不会给i一个无效的值。

所以虽然这段代码也是线程安全的:

 public void CallToMethodInOtherClass(List list) { Console.WriteLine(list[93]); // obviously only works if there's at least 94 items // but that's nothing to do with thread-safety } 

此代码不是线程安全的:

 public void CallToMethodInOtherClass(List list) { lock(locker)//same as in the question, different locker to that used elsewhere. { int i = list.Count; if(i > 93) Console.WriteLine(list[93]); } } 

在进一步说明之前, 我描述为线程安全的两个位不承诺由List的规范 。 保守的编码会假设它们不是线程安全的而不是依赖于实现细节,但我将依赖于实现细节,因为它影响了如何以一种重要方式使用锁的问题:

因为在list上运行的代码没有首先获取锁定器上的locker ,所以不会阻止该代码与CallToMethodInOtherClass同时运行。 现在,虽然list.Count是线程安全的, list[93]list.Count安全的,*我们依赖第一个的两者的组合,以确保第二个工作不是线程安全的。 因为锁外的代码可以影响list ,所以代码可以在Count之间调用RemoveClear来确保list[93]可以工作,并且list[93]被调用。

现在,如果我们知道list只是被添加到了,那很好,即使同时发生resize,我们最终也会得到list[93]的值。 如果某些内容正在写入list[93]并且它是.NET将以primefaces方式写入的类型(并且int就是这样一种类型),那么我们最终将使用旧的或新的类型,就像我们一样正确锁定我们将获得旧的或新的取决于哪个线程首先锁定。 同样,这是一个实现细节而不是指定的承诺 ,我只是指出这样做只是为了指出线程安全性仍然会导致非线程安全的代码。

将其转向真实代码。 我们不应该假设list.Countlist[93]是线程安全的,因为我们没有承诺他们会这样,而且可能会改变,但即使我们确实有这个承诺,这两个承诺也不会构成一个承诺他们一起是线程安全的。

重要的是使用相同的锁来保护可能相互干扰的代码块。 因此,请考虑以下保证为线程安全的变体:

 public class ThreadSafeList { private readonly object locker = new object(); private List myList = new List(); public void Add(int item) { lock(locker) myList.Add(item); } public void Clear() { lock(locker) myList.Clear(); } public int Count { lock(locker) return myList.Count; } public int Item(int index) { lock(locker) return myList[index]; } } 

该类保证在其所做的一切中都是线程安全的。 不依赖于任何实现细节,这里没有任何方法会破坏状态或给出不正确的结果,因为另一个线程正在对同一个实例做什么。 以下代码仍然不起作用:

 // (l is a ThreadSafeList visible to multiple threads. if(l.Count > 0) Console.WriteLine(l[0]); 

我们保证每次通话的线程安全100%,但我们无法保证组合,我们无法保证组合。

我们可以做两件事。 我们可以为组合添加一种方法。 对于许多专门为multithreading使用而设计的类,以下内容会很常见:

 public bool TryGetItem(int index, out int value) { lock(locker) { if(l.Count > index) { value = l[index]; return true; } value = 0; return false; } } 

这使得单个操作的计数测试和项目检索部分保证是线程安全的。

或者,通常我们需要做的是,我们将锁定发生在操作分组的地方:

 lock(lockerOnL)//used by every other piece of code operating on l if(l.Count > 0) Console.WriteLine(l[0]); 

当然,这会使ThreadSafeList的锁冗余,只是浪费精力,空间和时间。 这是大多数类不为其实例成员提供线程安全的主要原因 – 因为你不能有意义地保护类中的成员调用组,这是浪费时间尝试除非线程安全承诺非常明确,并且有用。

要回到您问题中的代码:

应该删除OtherClass的锁,除非OtherClass有自己内部锁定的原因。 它无法做出有意义的承诺,它不会以非线程安全的方式组合,并且向程序添加更多锁只会增加分析它的复杂性,以确保没有死锁。

CallToMethodInOtherClass的调用应该受到与该类中其他操作相同的锁的保护:

 public void MethodeB() { lock(locker) CallToMethodInOtherClass(myList); } 

然后,只要CallToMethodInOtherClass不在某个地方存储myList ,以后其他线程就可以看到它, CallToMethodInOtherClass不是线程安全的并不重要,因为唯一可以访问myList代码带来了自己保证不调用它与myList上的其他操作myList

两件重要的事情是:

  1. 当某些东西被描述为“线程安全”时,要知道它的前景是什么,因为存在不同类型的承诺属于“线程安全”并且它本身就意味着“我不会将此对象放入一个荒谬的国家“,虽然它是一个重要的组成部分,但它本身并不是很多。

  2. 锁定操作组,对每个影响相同数据的组使用相同的锁,并保护对对象的访问,以便不可能有另一个线程没有与此一起玩球。

*这是一个非常有限的线程安全定义。 在List上调用list[93]其中T是一个将被primefaces写入和读取的类型,我们不知道它是否实际上至少有94个项目同样安全,无论是否有其他线程在其上运行。 当然,在任何一种情况下它都可以抛出ArgumentOutOfRangeException并不是大多数人认为“安全”的事实,但我们对multithreading的保证与一个保持相同。 这是我们通过在单个线程中检查Count而不是在multithreading情况下获得更强的保证,这导致我将其描述为不是线程安全的; 虽然那个组合仍然不会腐败状态,但它可以导致一个例外,我们保证自己不可能发生。

屁股所有答案都说这些是不同的锁定对象。

一个简单的方法是拥有一个静态锁定对象f.ex:

 publc class A { public static readonly object lockObj = new object(); } 

并在两个类中使用锁如下:

 lock(A.lockObj) { } 

可能是最简单的方法

 public class A { private List myList; public A() { myList = new List() } private void MethodeA() { lock(myList) { myList.Add(10); } } public void MethodeB() { CallToMethodInOtherClass(myList); } } public class OtherClass { public CallToMethodInOtherClass(List list) { lock(list) { int i = list.Count; } } } 

许多答案提到使用静态只读锁。

但是,你真的应该尽量避免这种静态锁定。 在多个线程使用静态锁的情况下创建死锁会很容易。

您可以使用的是.net 4并发集合之一,它们代表您提供一些线程同步,因此您不需要使用锁定。

看一下System.collections.Concurrent命名空间。 对于此示例,您可以使用ConcurrentBag类。