Monitor 方法是靜態的,不需要生成Monitor 類的實例就可以直接調用它們。在.NET Framework 中,每個對象都有一個與之關聯的鎖,對象可以得到並釋放它以便於在任意時間只有一個線程可以訪問對象實例變量和方法。類似的,.NET Framework 中的每個對象都提供一個允許自己進入等待狀態的機制。與鎖的機制類似,這種機制的主要目的是為了實現線程間通信。當一個線程進入到一個對象的關鍵部分且需要一個特定條件並假設另外一個線程將會在同樣的關鍵區域中創建條件時這種機制才會發生。
現在比較特別的是在任意時間任意關鍵區域內僅允許有一個線程,而且當第一個線程進入到關鍵區域以后,其他線程都不能進入。現在問題來了,由於第一個線程已經獨占了關鍵區域,那么第二個線程如何在關鍵區域內創建一個條件呢? 舉個簡單例子來說明如何解決這個問題的:如果線程A需要從數據庫中獲取一些數據,線程B等待所有數據被接收以后處理這些數據,線程B調用Wait()方法來等待線程A在數據到來時通知它。當數據到來以后,線程A會調用Pulse() 方法來通知線程B數據已經到達現在可以處理數據了。這是通過“等待&通知”機制實現的。第一個線程進入關鍵區域並執行Wait()方法。Wait()方法將鎖釋放后進入等待狀態,然后第二個線程現在可以進入關鍵區域,改變相關條件,最后調用Pulse() 方法來通知等待線程條件已經滿足,現在可以繼續執行了。第一個線程再次獲得鎖,然后從Monitor.Wait() 方法返回並從Monitor.Wait()方法處繼續執行。
任意兩個線程都不可以同時進入Enter()方法。這與ATM 的例子類似:任意時間只允許一個人操作賬戶,其他人不可以操作直到第一個人離開。Monitor.Enter() 和 Monitor.Exit() 方法的命名是很貼近生活的。圖2 描述了Monitor 類的工作機制。
圖2
現在我們來看一個使用Enter() 和 Exit() 方法的例子,MonitorEnterExit.cs:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace MonitorEnterExit { public class EnterExit { private int result = 0; public void NonCriticalSection() { Console.WriteLine("Entered Thread " + Thread.CurrentThread.GetHashCode()); for (int i = 1; i <= 5; i++) { Console.WriteLine("Result = " + result++ + " ThreadID " + Thread.CurrentThread.GetHashCode()); Thread.Sleep(1000); } Console.WriteLine("Exiting Thread " + Thread.CurrentThread.GetHashCode()); } public void CriticalSection() { //Enter the Critical Section Monitor.Enter(this); NonCriticalSection(); //Exit the Critical Section Monitor.Exit(this); } public static void Main(string[] args) { EnterExit e = new EnterExit(); if (args.Length > 0) { Thread nt1 = new Thread(new ThreadStart(e.NonCriticalSection)); nt1.Start(); Thread nt2 = new Thread(new ThreadStart(e.NonCriticalSection)); nt2.Start(); } else { Thread ct1 = new Thread(new ThreadStart(e.CriticalSection)); ct1.Start(); Thread ct2 = new Thread(new ThreadStart(e.CriticalSection)); ct2.Start(); } Console.ReadLine(); } } }
當你不輸入任何參數時你將得到CriticalSection()方法的輸出,結果如下:
對應的,當你輸入一個參數時,將會得到NonCriticalSection() 方法的相關輸出:
上面的例子中,我們定義了一個EnterExit 類、一個全局變量和兩個方法:NonCriticalSection() 和 CriticalSection(). 在NonCriticalSection()部分我們沒有使用任何監視器來鎖住它,而在 CriticalSection()方法中使用監視器來鎖住關鍵部分。這兩個方法都修改結果值。
關鍵部分定義為Montor.Enter(this) 和 Monitor.Exit(this)之間的代碼塊。參數“this”表示鎖應該按考慮設置在當前對象。把哪個對象作為Enter()方法的參數總是很難決定。當你需要鎖住一個對象以便於其他線程不可以訪問這個對象時,可以把相應對象的引用作為Enter()方法的參數。例如,在我們之前討論的AccountWrapper例子中,我們把Account對象傳遞給Monitor, 而不是AccountWrapper對象的this指針。這是因為我們的目的是要鎖住Account對象而不是AccountWrapper對象。我們不想讓多個線程訪問Account對象,但是不關注是否有多個對象訪問AccountWrapper對象。
在Main()方法中,我們按照輸入的參數運行不同方法。如果沒有輸入參數,就是用CriticalSection()方法,反之則是用NonCriticalSection()方法。在兩種情況下,我們都有兩個線程在同一時間訪問方法並改變結果值。盡管他們是按順序定義的,For循環以及睡眠時間將保證線程完成資源。
通過對比關鍵部分和非關鍵部分的輸出可以讓關鍵部分的概念更清晰。如果你觀察NonCriticalSection()方法的輸出,線程nt1 和 nt2 都在同一時間改變變量值,最終輸出混亂結果。這是因為NonCriticalSection()方法沒有鎖,屬於非線程安全的。多個線程可以訪問這個方法並在同一時間獲得全局變量值。另外一方面,如果你觀察CriticalSection()方法的輸出,結果很清晰的顯示直到線程ct1退出關鍵部分,另外一個線程ct2才被允許進入關鍵部分。
下一篇將介紹Wait() 和 Pulse() 機制…