相信絕大多數.NET玩家和我一樣,常常使用Timer這個物件,而在WPF中使用DispatcherTimer的人也是很多,DispatcherTimer是在UI執行緒跑的。我們的程式中大多數都會充斥很多Timer,可以理解它是一個執行緒,它繼承自 System.Windows.Threading 。
程式中也許會有一些靜態變數或是單例模式的物件來讓不同的頁面進行互動,但也就是這樣讓每個執行緒之間打架提供了基礎。因為資源是單獨的,就像是腳踩兩隻船的人,必定會翻車。例如一個List集合,你在一個執行緒中對它進行了操作,在同步瞬間的另執行緒中,如果不對它謹慎處理,就會造成 “集合已修改;可能無法執行列舉操作”。當然我們說的不是關於集合的相關問題,而是關於資源分配的,當然在資源搶奪上,是在耗時的執行緒中才會出現的,例如下面的這張圖。
這種耗時的操作,並且在同步執行緒中,沒有對執行緒進行封裝,很容易造成資源搶奪問題,假如Object是個集合,我在中間把它改了,下一秒的其它執行緒對它進行髒讀了,就會產生錯誤,我們可以通過Lock關鍵字。
首先在Microsoft文件中對Lock的說明是,lock 關鍵字可以用來確保程式碼塊完成執行,而不會被其他執行緒中斷。這是通過在程式碼塊執行期間為給定物件獲取互斥鎖來實現的。
不過我們需要注意的是Lock本質上Monitor.Enter,Monitor.Enter會使值型別裝箱,每次Lock的是裝箱後的物件。Lock其實是類似編譯器的語法糖,因此編譯器直接限制住不能lock值型別,為啥呢,你仔細想想,每次裝箱後都是不同的物件,我怎麼判斷? object.ReferenceEquals 每次都是false...還有就是千萬不要Lock 字串,簡單來說Lock字串之後,只要是你以後有字串匹配和你Lock裡的內容有一樣的,那個該字串也會被鎖定,相當於死鎖了。
Lock和Monitor的區別不是很大,具體看以下程式碼。
private static object obj = new object(); public void LockSomething() { lock (obj) { dosomething(); } } public void MonitorSomeThing() { Monitor.Enter(obj); dosomething(); Monitor.Exit(obj); } public void dosomething() { //做具體的事情 }
lock和Monitor是.NET用一個特殊結構實現的,Monitor物件是完全託管的、完全可移植的,並且在作業系統資源要求方面可能更為有效,同步速度較快,但不能跨程式同步。主要作用是鎖定臨界區,使臨界區程式碼只能被獲得鎖的執行緒執行。Monitor.Wait和Monitor.Pulse用於執行緒同步,類似訊號操作,個人感覺使用比較複雜,容易造成死鎖。
lock就是封裝了Monitor.Enter和Monitor.Exit方法其實非常不難理解,只要確定Lock在啥時候用,該怎麼用就可以了,總結一句話。經常會應用於防止多執行緒操作導致公用變數值出現不確定的異常,用於確保操作的安全性。