使用MethodImplAttribute來做Singleton的注意事項
一般在使用Singleton Pattern(獨體模式)時,會寫類似下面的程式碼
public class Singleton
{
static object lockObj = new Object();
static Singleton instance;
private Singleton(){}
public static Singleton CreateInstance()
{
if(instance==null)
{
lock(lockObj)
{
if(instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
利用雙重鎖定的方式,限制初始化或是其他邏輯不會重複的執行
另外也可利用MethodImplAttribute搭配MethodImplOptions.Synchronized來實作
public class Singleton
{
static Singleton instance;
private Singleton(){}
[MethodImpl(MethodImplOptions.Synchronized)]
public static Singleton CreateInstance()
{
if(instance==null)
{
instance = new Singleton();
}
return instance;
}
}
MethodImplAttribute 是在 System.Runtime.CompilerServices 命名空間底下,用以指定方法的實作方式
搭配MethodImplOptions列舉型別,選擇Synchronized,其描述為
但其內部還是用lock的方式來實現,就如同MSDN上的描述一樣,對於static的方法,他會對型別做鎖定
以我們的例子來說,就是lock(typeof(Singleton))。對於非static的方法,則是鎖定執行個體,如lock(this)。
小問題:如果是靜態類別的非static的方法呢?那會鎖定什麼?(解答在最後)
但在使用上必須注意一些小地方,請看下面範例:(範例由LINQPad撰寫)
void Main()
{
Thread t1=new Thread(p=>{
TestClass.Excute();
});
Thread t2=new Thread(p=>{
TestClass.Excute();
});
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
public class TestClass
{
//[MethodImpl(MethodImplOptions.Synchronized)]
public static void Excute()
{
("Start : " + DateTime.Now).Dump();
Thread.Sleep(1000);
("End : " + DateTime.Now).Dump();
}
}
範例的情況是,用兩個執行序分別執行TestClass的static Excute方法,並在方法內部Sleep一秒鐘
在還沒有鎖定的情況下,兩個執行序幾乎是同時間進行動作,因此印出來的時間會一致
但如果把MethodImlp的註解拿掉的話,則執行後會是下面的結果
t2執行序會因為lock的關係,等到t1執行完之後才動作,所以可以看到時間差了一秒。
所以要是一個類別中有兩個靜態方法同時都套用了[MethodImpl(MethodImplOptions.Synchronized)]
或是用同一個執行個體在不同執行序呼叫套用了上述Attribute的方法的話,就會互相影響到。
另外MSDN上也有注意事項
也就是說public的類別,別人也有可能會用lock去鎖定型別或執行個體,因此也是要小心這點。
解答:不能在靜態類別中宣告執行個體成員…也就是靜態類別中不會有非靜態的方法…(逃)