2011-08-29 88 views
33

C#不允許鎖定空值。我想我可以在鎖定之前檢查該值是否爲null,但是因爲我沒有鎖定它,另一個線程可能會出現並將值設爲null!我怎樣才能避免這種競爭狀態?爲什麼C#不允許空值被鎖定?

+4

你爲什麼不只是使用靜態初始化的一員,*始終不爲空* – zerkms

+2

據我瞭解,空本質上不過。你怎麼能鎖上什麼?換句話說,string myString = null聲明瞭一個字符串類型的變量,但這就是它的全部內容 - 它不作爲一個對象存在,因爲它沒有任何價值。 – Tim

回答

23

鎖定一個永不爲空的值,例如

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

另外要注意避免雙重檢查鎖定這個有趣的比賽條件:Memory Model Guarantees in Double-checked Locking

+3

可能很高興將只讀添加到鎖對象以調用所需的不變性 – cordialgerm

+0

爲什麼鎖_lockOnMe可以阻止其他人訪問_iMightBeNull? –

+0

-1:你的代碼仍然容易受到你鏈接到的競爭條件的影響; '_iMightBeNull'需要聲明爲volatile。或者,最好是隻使用'懶惰的'進行延遲初始化。 – Douglas

5

這裏有兩個問題:

首先,不要在null對象鎖定。它沒有任何意義,因爲兩個對象null如何區分?

二,安全初始化在多線程環境變量,可以使用雙重檢查鎖定模式:

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

這將確保另一個線程尚未初始化的對象,可用於實現單身模式。

48

不能對空值鎖定,因爲CLR沒有地方的同步塊重視,這是允許的CLR同步通過Monitor.Enter /退出鍵訪問任意對象(這是什麼lock內部使用)

+0

+1:這是最正確的答案。 –

1

爲什麼C#不允許空值被鎖定?

Paul's answer是迄今爲止唯一技術上正確的,所以我會接受那一個。這是因爲.NET中的監視器使用附加到所有參考類型的同步塊。如果你有一個變量null那麼它不是指任何對象,這意味着監視器無法訪問可用的同步塊。

我該如何避免這種競爭狀態?

傳統的方法是鎖定一個對象引用,你知道永遠不會是null。如果您發現自己處於無法得到保證的情況,那麼我會將其歸類爲非傳統方法。除非您更詳細地描述可導致可空鎖定目標的特定情況,否則實際上我沒有太多可以提及的地方。

1

您的問題的第一部分已經回答了,但我想爲您的問題的第二部分添加一些內容。

使用不同的對象來執行鎖定更爲簡單,特別是在這種情況下。 這也解決了維護關鍵部分中多個共享對象的狀態的問題,例如,員工名單和員工照片列表。

此外,這種技術也是有用的,當你必須獲得對原始類型的鎖如int或十進制等

在我看來,如果你使用這個技術,因爲其他人建議,那麼你並不需要執行空檢查兩次。例如在接受的答案克里斯已經使用,如果條件兩次真的沒有任何區別,因爲鎖定的對象是不同的,那麼實際上正在修改,如果你鎖定在一個不同的對象然後執行第一個空檢查是無用的和浪費CPU 。

我會建議下面的一段代碼;

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個問題,一個在鎖定之前不需要空的檢查,其次它在不鎖定兩個共享源的情況下創建關鍵部分,並且第三個鎖定多個對象由於寫入時缺乏注意力而導致死鎖碼。

以下是我如何使用原始類型上的鎖。

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; 
} 

快樂線程:)

相關問題