摘要
今天在園子里面有園友反饋關於[C#基礎]說說lock到底鎖誰?文章中lock(this)的問題。后來針對文章中的例子,仔細想了一下,確實不准確,才有了這篇文章的補充,已經對文章中的demo進行修改。
lock(this)
一個例子
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace LockTest { class Program { static void Main(string[] args) { TestLock testlock = new TestLock(); Thread th = new Thread(() => { //模擬死鎖:造成死鎖,使lock無法釋放,在i=5時,跳出死循環,釋放lock testlock.DoWorkWithLock(); }); th.Start(); Thread.Sleep(1000); Thread th2 = new Thread(() => { //這個地方你可能會有疑惑,但存在這種情況,比如你封裝的dll,對其它開發人員不是可見的 //開發人員很有可能在他的邏輯中,加上一個lock保證方法同時被一個線程調用,但這時有其它的線程正在調用該方法, //但並沒有釋放,死鎖了,那么在這里就不會被執行,除非上面的線程釋放了lock鎖定的對象。這里的lock也可以理解為一個標識,線程1被鎖定的對象
//是否已經被釋放,
//如果沒有釋放,則無法繼續訪問lock塊中的代碼。 lock (testlock) { // 如果該對象中lock(this)不釋放(testlock與this指的是同一個對象),則其它線程如果調用該方法,則會出現直到lock(this)釋放后才能繼續調用。 testlock.MotherCallYouDinner(); testlock.DoWorkWithLock(); } }); th2.Start(); Console.Read(); } } class TestLock { public static readonly object objLock = new object(); /// <summary> /// 該方法,希望某人在工作的時候,其它人不要打擾(希望只有一個線程在執行) /// </summary> /// <param name="methodIndex"></param> public void DoWorkWithLock() { //鎖當前對象 lock (this) { Console.WriteLine("lock this"); int i = 0; while (true) { Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(1000); if (i == 5) { break; } Console.WriteLine(i.ToString()); i++; } } Console.WriteLine("lock dispose"); } public void MotherCallYouDinner() { Console.WriteLine("Your mother call you to home for dinner."); } } }
測試
demo說明:main方法中,創建了一個對象testlock對象,線程1執行該對象的DoWorkWithLock方法,因為死鎖(5s后釋放),造成lock(this)無法釋放,則導致了方法MotherCallYouDinner,DoWorkWithLock在線程2中無法被調用,直到lock(this)釋放,lock(testlock)才能繼續執行,可以這么理解,由於鎖定的同一個對象,線程1釋放了鎖定的對象,其它線程才能訪問。
lock(static readonly object)
那么通過lock(static object)方式呢,能不能保證lock塊內的方法,同時只被一個線程執行呢,並且線程2能訪問到MotherCallYouDinner方法。而不像上面出現的那種情況,如果不釋放lock(this),導致線程2都無法執行代碼邏輯。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace LockTest { class Program { static void Main(string[] args) { TestLock testlock = new TestLock(); Thread th = new Thread(() => { //模擬死鎖:造成死鎖,使lock無法釋放,在i=5時,跳出死循環,釋放lock testlock.DoWorkWithLock(); }); th.Start(); Thread.Sleep(1000); Thread th2 = new Thread(() => { lock (testlock) { testlock.MotherCallYouDinner(); testlock.DoWorkWithLock(); } }); th2.Start(); Console.Read(); } } class TestLock { public static readonly object objLock = new object(); /// <summary> /// 該方法,希望某人在工作的時候,其它人不要打擾(希望只有一個線程在執行) /// </summary> /// <param name="methodIndex"></param> public void DoWorkWithLock() { //鎖 lock (objLock) { Console.WriteLine("lock this"); int i = 0; while (true) { Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(1000); if (i == 5) { break; } Console.WriteLine(i.ToString()); i++; } } Console.WriteLine("lock dispose"); } public void MotherCallYouDinner() { Console.WriteLine("Your mother call you to home for dinner."); } } }
測試
可以看到,將lock(this)更換為鎖定私有的靜態對象,線程2執行了,首先輸出了“Your mother call you to home for dinner.”,同時實現了DoWorkWithLock方法中lock的代碼塊當前只被一個線程執行,直到lcok(objlock)被釋放。因為鎖定的對象,外部不能訪問,線程2不再關心lock(this)是不是已經釋放,都會執行,當然也保證了方法DoWorkWithLock同時被一個線程訪問。
總結
1、避免使用lock(this),因為無法保證你提供的方法,在外部類中使用的時候,開發人員會不會鎖定當前對象。
通常,應避免鎖定 public 類型,否則實例將超出代碼的控制范圍。常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此准則:
如果實例可以被公共訪問,將出現 lock (this) 問題。
如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題。
由於進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock(“myLock”) 問題。
最佳做法是定義 private 對象來鎖定, 或 private static 對象變量來保護所有實例所共有的數據。
這里只是說明lock(this)的問題,雖然極端。但開發中,不能保證不會發生。
2、最好使用私有的靜態只讀的鎖對象,保證不會影響其他邏輯的正常執行。
3、盡量避免死鎖的發生。
資料,如果仍然不明白可以參考下面的鏈接
非常感謝,@咕-咚 在上篇文章中,對demo提出的疑問,引發進一步的思考。當然,如果還有不妥的地方,歡迎指正,討論。