Monitor裡邊有一些static方法,可以用於在物件上獲取同步鎖,來進行一些程式同步控制操作
用法及注意點如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace myTest { class Program { // 一個比較容易犯的錯誤 // 使用 Monitor 鎖定物件(即引用型別)而不是值型別。將值型別變數傳遞給 Enter 時, //它被裝箱為物件。如果再次將相同的變數傳遞給 Enter,則它被裝箱為一個單獨物件,而且執行緒不會阻止。Monitor //本應保護的程式碼未受保護。此外,將變數傳遞給 Exit 時, //也建立了另一個單獨物件。因為傳遞給 Exit 的物件和傳遞給 Enter 的物件不同,Monitor //將引發 SynchronizationLockException //這種情況最好用 Interlocked 來完成 private static int _num = 1; // 裝箱一下就可以了 private static object num = _num; static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(addNum)); t1.Name = "執行緒1"; Thread t2 = new Thread(new ThreadStart(addNum)); t2.Name = "執行緒2"; t1.Start(); t2.Start(); Console.ReadLine(); } //|- 擁有鎖的執行緒 lockObj->|- 就緒佇列(ready queue) |- 等待佇列(wait queue) // 就緒佇列:嘗試lock物件的執行緒 // 等待佇列:在等待中, !!!不會主動!!! 去lock物件的執行緒 Monitor.wait 會使執行緒進入等待佇列 // 如果只呼叫wait不呼叫pulse,可能使執行緒進入死鎖 // 下面執行的時間線: t1獲得鎖——t1列印——2000ms——t1Pulse(此時無執行緒在等待佇列,故無效)——2000ms——t1釋放鎖並進入等待佇列—— // t2獲得鎖——t2列印——2000ms——t2Pulse(此時t1執行緒在等待佇列,t1進入就緒佇列)——2000ms——t2釋放鎖並進入等待佇列—— // t1獲得鎖——t1Pulse(此時t2執行緒在等待佇列,t2進入就緒佇列)——t1列印——t1釋放鎖——t1退出—— // t2獲得鎖——t2Pulse(此時無執行緒在等待佇列,故無效)——t2列印——t2釋放鎖——t2退出 private static void addNum() { Boolean gotLock = false; try { // Monitor.Enter(num); //獲取排它鎖 Monitor.Enter(num, ref gotLock); Console.WriteLine(Thread.CurrentThread.Name+ DateTime.Now.ToString() + "——————" + num); //釋放鎖並讓執行緒進入等待佇列,直到它重新獲得鎖 Thread.Sleep(2000); //通知等待的執行緒進入就緒佇列,有鎖了 Monitor.Pulse(num); Thread.Sleep(2000); Monitor.Wait(num); Monitor.Pulse(num); } finally { Console.WriteLine(Thread.CurrentThread.Name + DateTime.Now.ToString() + "——————" + num); if (gotLock) { Monitor.Exit(num); } } } } }
執行結果:
用Monitor類獲得物件鎖的try .. catch finally的過程還有一個語法糖,lock關鍵字