使用MethodImplAttribute來做Singleton的注意事項

使用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,其描述為

image

 

但其內部還是用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一秒鐘

在還沒有鎖定的情況下,兩個執行序幾乎是同時間進行動作,因此印出來的時間會一致

image

但如果把MethodImlp的註解拿掉的話,則執行後會是下面的結果

image

t2執行序會因為lock的關係,等到t1執行完之後才動作,所以可以看到時間差了一秒。

 

 

 

所以要是一個類別中有兩個靜態方法同時都套用了[MethodImpl(MethodImplOptions.Synchronized)]

或是用同一個執行個體在不同執行序呼叫套用了上述Attribute的方法的話,就會互相影響到。

 

另外MSDN上也有注意事項

image

也就是說public的類別,別人也有可能會用lock去鎖定型別或執行個體,因此也是要小心這點。

 

解答:不能在靜態類別中宣告執行個體成員…也就是靜態類別中不會有非靜態的方法…(逃)