为什么C#不允许空值被locking?

C#不允许locking空值。 我想我可以在locking之前检查这个值是否为null,但是因为我没有locking它,另外一个线程可能会出现,并将值设为null! 我怎样才能避免这种竞争条件?

locking一个从不为空的值,例如

Object _lockOnMe = new Object(); Object _iMightBeNull; public void DoSomeKungFu() { if (_iMightBeNull == null) { lock (_lockOnMe) { if (_iMightBeNull == null) { _iMightBeNull = ... whatever ...; } } } } 

还要小心避免这种双重locking的有趣的竞争条件:双重locking内存模型保证

你不能locking一个空值,因为CLR没有地方附加SyncBlock,这就是允许CLR通过Monitor.Enter / Exit(这是内部使用的lock )同步访问任意对象,

这里有两个问题:

首先,不要locking一个null对象。 这两个对象如何能够被区分是null

其次,要在multithreading环境中安全地初始化variables,请使用双重检查的locking模式:

 if (o == null) { lock (lockObj) { if (o == null) { o = new Object(); } } } 

这将确保另一个线程尚未初始化该对象,并可用于实现Singleton模式。

为什么C#不允许空值被locking?

保罗的答案是迄今唯一在技术上正确的答案 ,所以我会接受这一答案 。 这是因为.NET中的监视器使用附加到所有引用types的同步块。 如果你有一个null的variables,那么它不是指任何对象,这意味着监视器不能访问一个可用的同步块。

我怎样才能避免这种竞争条件?

传统的方法是locking一个对象引用,你知道永远不会为null 。 如果您发现自己处于无法保证的状态,那么我会将这种非传统方法分类。 除非您更详细地描述可导致可空locking目标的特定情况,否则实际上我可以在此处提及更多。

你的问题的第一部分已经回答了,但是我想为你的问题的第二部分添加一些内容。

使用不同的对象执行locking更为简单,特别是在这种情况下。 这也解决了维护关键部分中的多个共享对象的状态的问题,例如员工列表和员工照片列表。

此外,当你必须获取原始types的锁时,这种技术也是有用的,例如int或者decimal等。

在我看来,如果你正在使用这个技巧,就像其他人所build议的一样,那么你不需要执行两次空检查。 例如在被接受的答案Cris中使用,如果条件两次真的没有任何区别,因为locking的对象是不同的,那么实际上是修改,如果你locking在一个不同的对象,然后执行第一个空检查是没有用的,浪费中央处理器。

我会build议下面的一段代码;

 object readonly syncRootEmployee = new object(); List<Employee> employeeList = null; List<EmployeePhoto> employeePhotoList = null; public void AddEmployee(Employee employee, List<EmployeePhoto> photos) { lock (syncRootEmployee) { if (employeeList == null) { employeeList = new List<Employee>(); } if (employeePhotoList == null) { employeePhotoList = new List<EmployeePhoto>(); } employeeList.Add(employee); foreach(EmployeePhoto ep in photos) { employeePhotoList.Add(ep); } } } 

在这里如果有人看到比赛情况,我不能在这里看到任何竞赛状况,请在评论中回应。 正如你在上面的代码中看到的那样,它立刻解决了3个问题,一个在locking之前不需要空的检查,其次是在不locking两个共享源的情况下创build临界区,第三个locking多个对象,由于写入时缺乏注意力而导致死锁码。

以下是我如何使用原始types的锁。

 object readonly syncRootIteration = new object(); long iterationCount = 0; long iterationTimeMs = 0; public void IncrementIterationCount(long timeTook) { lock (syncRootIteration) { iterationCount++; iterationTimeMs = timeTook; } } public long GetIterationAvgTimeMs() { long result = 0; //if read without lock the result might not be accurate lock (syncRootIteration) { if (this.iterationCount > 0) { result = this.iterationTimeMs / this.iterationCount; } } return result; } 

快乐线程:)