這是一箇舊瓶新裝的話題。只是將我今日的所見所思予以實踐和整理,以備往後所用。同時也提供給大家,希望能有所協助。
A 從單例模式說起
代碼如下:
class Program
{
static void Main(string[] args)
{
Singleton demo1 = Singleton.Init();
Singleton demo2 = Singleton.Init();
}
}
public class Singleton
{
private Singleton() { }
private static Singleton instance = null;
private static readonly object singleObj = new object();
public static Singleton Init()
{
if (null == instance)
{
lock (singleObj) //singleObj 不能使用instance 代替
{
if (null == instance)
{
instance = new Singleton();
}
}
}
return instance;
}
}
關於單例模式,大家可以參考:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html
B 關於lock 方法
在以上單列模式的代碼中:
如果將: private static readonly object singleObj = new object();
修改為: private static readonly object singleObj = null;
將在lock( ) 方法處,拋出未處理的異常:System.ArgumentNullException: 值不可為空!
● lock 關鍵字將語句塊標記為臨界區;
● lock( obj ) 方法中的obj 對象為:擷取獨佔鎖定的指定對象。
obj 對象為null 將導致 ArgumentNullException 異常。
● lock 方法在MSIL 中會被編譯成 Monitor.Enter( ) 和 Monitor.Exit( ) 。例如:
public static void MyLock()
{
lock (typeof(Program))
{
}
}
以上代碼通過lock 語句使MyLock 同步,這個方法被編譯成MSIL 後,代碼如所示:
從被標註的地區可以看到:一條lock 語句被編譯成了調用Monitor 的Enter 和Exit 的方法。
lock 的功能就相當於直接調用Monitor 的Entry 方法,並在結束後會自動調用Monitor 的Exit 方法解除鎖定。
C 關於Monitor 類
● Monitor 類屬於System.Threading 命名空間;
● Monitor 類提供同步對對象的訪問的機制;
● 使用 Monitor 鎖定對象(即參考型別)而不是實值型別;
● Monitor 類型對於多線程操作是安全的;
Monitor 類的樣本一:
class Program2
{
static void Main(string[] args)
{
MyMonitor1 mon_1 = new MyMonitor1();
mon_1.Test();
}
}
class MyMonitor1
{
private object obj = new object();
public void Test()
{
//開始鎖定
System.Threading.Monitor.Enter(obj);
try
{
//lock 的地區
}
catch (Exception e)
{
// 異常處理代碼
}
finally
{
//解除鎖定
System.Threading.Monitor.Exit(obj);
}
}
}
Monitor 類的樣本二:
class Program3
{
static void Main(string[] args)
{
//多個線程調用Test方法
Thread t1 = new Thread(MyMonitor2.Test);
Thread t2 = new Thread(MyMonitor2.Test);
t1.Start();
t2.Start();
}
}
class MyMonitor2
{
private static object obj = new object();
public static void Test()
{
//使用TryEntry方法設定一個鎖定逾時
if (Monitor.TryEnter(obj, 2000))
{
try
{
Console.WriteLine("等待4秒開始");
Thread.Sleep(4000);
Console.WriteLine("等待4秒結束");
}
finally
{
//解除鎖定
Monitor.Exit(obj);
}
}
else
{
Console.WriteLine("已逾時2秒!");
}
}
}
D 關於同步與互斥
關於同步的問題,可以使用Monitor 類來解決。
在使用Monitor 類的時候,建議將Monitor.Enter( ) 方法替換成Monitor.TryEnter( ) 方法。
使用Monitor.Enter( ) 方法時,代碼如下:
Monitor.Entry(lockObj);
try
{
// lockObj的同步區
}
catch(Exception e)
{
// 異常處理代碼
}
finally
{
Monitor.Exit(lockObj); // 解除鎖定
}
注意:如果直接在C#來源程式中使用Monitor類,就必須調用Exit方法來顯式地解除鎖定。
使用Monitor.TryEnter( ) 方法時,代碼如下:
if(Monitor.TryEntry(lockObj, 1000))
{
try
{
}
finally
{
Monitor.Exit(lockObj);
}
}
else
{
// 逾時後的處理代碼
}
注意:使用TryEntry方法設定一個鎖定逾時,單位是毫秒。
上面的代碼設定了鎖定逾時時間為1秒。
如果在1秒鐘之內,lockObj 還未被解鎖,TryEntry 方法就會返回 false;
如果在1秒鐘之內,lockObj 被解鎖,TryEntry 方法就會返回 true。
這樣,可以使用TryEntry 方法來避免死結。
同步與互斥 樣本一:
class Program4
{
static void Main(string[] args)
{
Thread A = new Thread(TestClass1.GetA);
A.Name = "Thread_A ";
Thread B = new Thread(TestClass1.GetB);
B.Name = "Thread_B ";
A.Start();
B.Start();
}
}
class TestClass1
{
private static object resource_A = new object();
private static object resource_B = new object();
public static void GetA()
{
MyWrite("in GetA()");
if (Monitor.TryEnter(resource_A, 2000))
{
MyWrite("get resource_A");
GetB();
Thread.Sleep(2000);
Monitor.Exit(resource_A);
MyWrite("exit resource_A");
}
else
{
MyWrite("no has resource_A");
}
}
public static void GetB()
{
MyWrite("in GetB()");
if (Monitor.TryEnter(resource_B, 1000))
{
MyWrite("get resource_B");
GetA();
Thread.Sleep(1000);
Monitor.Exit(resource_B);
MyWrite("exit resource_B");
}
else
{
MyWrite("no has resource_B");
}
}
//自訂列印方法
private static void MyWrite(string str)
{
Console.WriteLine(Thread.CurrentThread.Name + str);
}
}
結果如下:
同步與互斥 樣本二:
class Program5
{
static void Main(string[] args)
{
Thread A = new Thread(TestClass2.GetA);
A.Name = "Thread_A ";
Thread B = new Thread(TestClass2.GetB);
B.Name = "Thread_B ";
A.Start();
B.Start();
}
}
class TestClass2
{
//獨佔鎖定的對象 A
private static object resource_A = new object();
//獨佔鎖定的對象 B
private static object resource_B = new object();
public static void GetA()
{
MyWrite("in GetA()");
if (Monitor.TryEnter(resource_A, 1000))
{
MyWrite("get resource_A");
Thread.Sleep(1000);
//GetB();
if (Monitor.TryEnter(resource_B, 2000))
{
Monitor.Exit(resource_B);
}
Monitor.Exit(resource_A);
MyWrite("exit resource_A");
}
else
{
MyWrite("no has resource_A");
}
}
public static void GetB()
{
MyWrite("in GetB()");
if (Monitor.TryEnter(resource_B, 1000))
{
MyWrite("get resource_B");
Thread.Sleep(1000);
//GetA();
if (Monitor.TryEnter(resource_A, 2000))
{
Monitor.Exit(resource_A);
}
Monitor.Exit(resource_B);
MyWrite("exit resource_B");
}
else
{
MyWrite("no has resource_B");
}
}
//自訂列印方法
private static void MyWrite(string str)
{
Console.WriteLine(Thread.CurrentThread.Name + str);
}
}
結果如下:
參考文章:
淺談c#中使用lock的是與非 作者:Jeff Wong
同步技術之Monitor 作者:銀河使者
樣本下載