Design Pattern : Singleton Pattern
作者:JB
關於「Singleton」, 『C# in Depth』已經描述了非常詳盡的細節。
我比較喜歡拿前輩的例子:取號機。
如果郵局有兩台取號機,如何保證同時兩台機器不會取到相同的號碼呢?
Singleton
Pattern目的在於設立一個只能被建立一次instance的Singeton class,也就是說,如果我們在程式裡要嘗試建立第二個該Singeton class的instance時,是不被允許的。
本範例目的在於實做一支有四支執行緒的取號程式(亦即四個取號機同時取號)。
測試以下四種Singleton Pattern:
1.
Non thread-safe
2.
Thread-safe using double-check locking
3.
Thread-safe, eager singleton
4.
Thread-safe, lazy singleton
一、 以C# Windows Form應用程式為例,建立專案後,加入以下四個Singleton class。
1.
Non thread-safe
備註2. 由建構子的輸出,可在runtime看出此類別實際上被建立了幾個instance。
備註3. GetInstance這個事件,目的在於建立或取得靜態的instance。
Non thread-safe指的就是這一段。
public sealed class Counter01
{
static Counter01 instance = null;
private int CNT = 0;
Counter01() //建構子
{
Console.WriteLine("建立Counter01物件!");
}
public static Counter01 GetInstance //建立或取得instance
{
get
{
if (instance == null)
{
instance = new Counter01();
}
return instance;
}
}
public int GetNumber() //取得新號碼
{
CNT++;
return CNT;
}
}
備註1. 宣告關鍵字sealed class 表示此類別不可被繼承,詳見MSDN。備註2. 由建構子的輸出,可在runtime看出此類別實際上被建立了幾個instance。
備註3. GetInstance這個事件,目的在於建立或取得靜態的instance。
Non thread-safe指的就是這一段。
2.
Thread-safe using
double-check locking
static Counter02 instance = null;
static readonly object padlock = new object(); //用來LOCK建立instance的程序。
private int CNT = 0;
Counter02()
{
Console.WriteLine("建立Counter02物件!");
}
public static Counter02 GetInstance
{
get
{
if(instance==null)
{
lock(padlock) //lock此區段程式碼,讓其它thread無法進入。
{
if(instance==null)
{
instance = new Counter02();
}
}
}
return instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
這邊跟第一種寫法的不同處,是使用lock(object) 來確定只有一條thread可以建立instance。
static Counter02 instance = null;
static readonly object padlock = new object(); //用來LOCK建立instance的程序。
private int CNT = 0;
Counter02()
{
Console.WriteLine("建立Counter02物件!");
}
public static Counter02 GetInstance
{
get
{
if(instance==null)
{
lock(padlock) //lock此區段程式碼,讓其它thread無法進入。
{
if(instance==null)
{
instance = new Counter02();
}
}
}
return instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
這邊跟第一種寫法的不同處,是使用lock(object) 來確定只有一條thread可以建立instance。
3.
Thread-safe, eager
singleton
因為第二種寫法過於繁雜,故有大師再改寫為第三種,亦即在類別裡面,直接宣告了instance = new Counter03,所以當我們使用Counter03時,instance會隨即建立, 所以稱為eager (急性子的) singleton。
因為宣告為靜態的的變數,每支ap只會建立一次,所以它是thread-safe。
public sealed class Counter03
{
static Counter03 instance = new Counter03();
private int CNT = 0;
Counter03()
{
Console.WriteLine("建立Counter03物件!");
}
public static Counter03 GetInstance
{
get
{
return instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
}
備註1. 因為已經事先建立了instance,所以GetInstance並不用考慮建立instance的事情。
因為第二種寫法過於繁雜,故有大師再改寫為第三種,亦即在類別裡面,直接宣告了instance = new Counter03,所以當我們使用Counter03時,instance會隨即建立, 所以稱為eager (急性子的) singleton。
因為宣告為靜態的的變數,每支ap只會建立一次,所以它是thread-safe。
public sealed class Counter03
{
static Counter03 instance = new Counter03();
private int CNT = 0;
Counter03()
{
Console.WriteLine("建立Counter03物件!");
}
public static Counter03 GetInstance
{
get
{
return instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
}
備註1. 因為已經事先建立了instance,所以GetInstance並不用考慮建立instance的事情。
4.
Thread-safe, lazy singleton
第四種應該是終極版了,為了改善eager songleton的效能,大師們又想出了利用 inner class(也就是nested class)來建立instance。 當我們在呼叫GetInstance時,才會去建立這個inner class裡面的靜態instance。
同樣的,因為是宣告為internal static readonly Counter04 instance,所以它是唯一,所以是thread-safe。
public sealed class Counter04
{
private int CNT = 0;
Counter04()
{
Console.WriteLine("建立Counter04物件!");
}
public static Counter04 GetInstance
{
get
{
return InnerClass.instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
class InnerClass
{
static InnerClass()
{
}
internal static readonly Counter04 instance = new Counter04();
}
}
第四種應該是終極版了,為了改善eager songleton的效能,大師們又想出了利用 inner class(也就是nested class)來建立instance。 當我們在呼叫GetInstance時,才會去建立這個inner class裡面的靜態instance。
同樣的,因為是宣告為internal static readonly Counter04 instance,所以它是唯一,所以是thread-safe。
public sealed class Counter04
{
private int CNT = 0;
Counter04()
{
Console.WriteLine("建立Counter04物件!");
}
public static Counter04 GetInstance
{
get
{
return InnerClass.instance;
}
}
public int GetNumber()
{
CNT++;
return CNT;
}
class InnerClass
{
static InnerClass()
{
}
internal static readonly Counter04 instance = new Counter04();
}
}
5.
其實在『C#
in Depth』,有提到.NET4.0的新寫法:
Lazy<T> type。
二、 建立取號機(Thread)
建立了四種Singleton class後,我們接著來寫Multi-thread的部分,下面只列出部分程式碼囉。
目的是建立四台取號機,每台取號機各取五個號碼。 如果是thread-safe,四台取號機不會拿到相同的號碼,而且最大的號碼是20。
建立了四種Singleton class後,我們接著來寫Multi-thread的部分,下面只列出部分程式碼囉。
目的是建立四台取號機,每台取號機各取五個號碼。 如果是thread-safe,四台取號機不會拿到相同的號碼,而且最大的號碼是20。
2.
建立Global Thread集合、ThreadStart
private List<Thread> myThread;
ThreadStart Starter;
private List<Thread> myThread;
ThreadStart Starter;
3.
在每個按鈕Click事件裡面,指定ThreadStart對應的事件 (指定取號方法的類別
Singleton class),然後再執行步驟4.
private void bt_Add1_Click(object sender, EventArgs e)
{
Starter = new ThreadStart(ThreadWork01);
Print();
}
/// <summary>
/// ThreadStart事件
/// </summary>
private void ThreadWork01()
{
for (int i = 1; i <= 5; i++)
{
int value = Counter01.GetInstance.GetNumber(); //指定取值方法的類別為Counter01
String sOutput = String.Format("{0} {1}-->號碼:{2}",DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), Thread.CurrentThread.Name, value);
Console.WriteLine(sOutput);
Thread.Sleep(1000);
}
}
private void bt_Add1_Click(object sender, EventArgs e)
{
Starter = new ThreadStart(ThreadWork01);
Print();
}
/// <summary>
/// ThreadStart事件
/// </summary>
private void ThreadWork01()
{
for (int i = 1; i <= 5; i++)
{
int value = Counter01.GetInstance.GetNumber(); //指定取值方法的類別為Counter01
String sOutput = String.Format("{0} {1}-->號碼:{2}",DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), Thread.CurrentThread.Name, value);
Console.WriteLine(sOutput);
Thread.Sleep(1000);
}
}
4.
建立四台取號機(四條Thread),然後開始取號(Thread.Start())
private void Print()
{
for (int index = 0; index < 4; index++)
{
Thread thread;
thread = new Thread(Starter); //指定ThreadStart事件
thread.Name = "取號機-" + index.ToString();
myThread.Add(thread);
thread = null;
}
foreach (Thread thread in myThread)
{
thread.Start();
}
}
private void Print()
{
for (int index = 0; index < 4; index++)
{
Thread thread;
thread = new Thread(Starter); //指定ThreadStart事件
thread.Name = "取號機-" + index.ToString();
myThread.Add(thread);
thread = null;
}
foreach (Thread thread in myThread)
{
thread.Start();
}
}
三、 執行結果
1.
Non thread-safe
建立Counter01物件!
建立Counter01物件!
2012/04/23 11:31:22 取號機-1-->號碼:1
2012/04/23 11:31:22 取號機-2-->號碼:2
2012/04/23 11:31:22 取號機-0-->號碼:1
2012/04/23 11:31:22 取號機-3-->號碼:3
…
2012/04/23 11:31:26 取號機-2-->號碼:18
2012/04/23 11:31:26 取號機-0-->號碼:19
可清楚的看到,Counter01被建立了兩次,所以出現取到兩次號碼 : 1
建立Counter01物件!
建立Counter01物件!
2012/04/23 11:31:22 取號機-1-->號碼:1
2012/04/23 11:31:22 取號機-2-->號碼:2
2012/04/23 11:31:22 取號機-0-->號碼:1
2012/04/23 11:31:22 取號機-3-->號碼:3
…
2012/04/23 11:31:26 取號機-2-->號碼:18
2012/04/23 11:31:26 取號機-0-->號碼:19
可清楚的看到,Counter01被建立了兩次,所以出現取到兩次號碼 : 1
2.
Thread-safe using double-check locking
建立Counter02物件!
2012/04/23 11:35:44 取號機-2-->號碼:3
2012/04/23 11:35:44 取號機-1-->號碼:2
2012/04/23 11:35:44 取號機-0-->號碼:1
2012/04/23 11:35:44 取號機-3-->號碼:4
2012/04/23 11:35:45 取號機-2-->號碼:6
2012/04/23 11:35:45 取號機-1-->號碼:5
2012/04/23 11:35:45 取號機-0-->號碼:7
2012/04/23 11:35:45 取號機-3-->號碼:8
2012/04/23 11:35:46 取號機-2-->號碼:10
2012/04/23 11:35:46 取號機-1-->號碼:9
2012/04/23 11:35:46 取號機-0-->號碼:11
2012/04/23 11:35:46 取號機-3-->號碼:12
2012/04/23 11:35:47 取號機-1-->號碼:14
2012/04/23 11:35:47 取號機-2-->號碼:13
2012/04/23 11:35:47 取號機-3-->號碼:15
2012/04/23 11:35:47 取號機-0-->號碼:16
2012/04/23 11:35:48 取號機-2-->號碼:17
2012/04/23 11:35:48 取號機-1-->號碼:18
2012/04/23 11:35:48 取號機-0-->號碼:19
2012/04/23 11:35:48 取號機-3-->號碼:20
建立Counter02物件!
2012/04/23 11:35:44 取號機-2-->號碼:3
2012/04/23 11:35:44 取號機-1-->號碼:2
2012/04/23 11:35:44 取號機-0-->號碼:1
2012/04/23 11:35:44 取號機-3-->號碼:4
2012/04/23 11:35:45 取號機-2-->號碼:6
2012/04/23 11:35:45 取號機-1-->號碼:5
2012/04/23 11:35:45 取號機-0-->號碼:7
2012/04/23 11:35:45 取號機-3-->號碼:8
2012/04/23 11:35:46 取號機-2-->號碼:10
2012/04/23 11:35:46 取號機-1-->號碼:9
2012/04/23 11:35:46 取號機-0-->號碼:11
2012/04/23 11:35:46 取號機-3-->號碼:12
2012/04/23 11:35:47 取號機-1-->號碼:14
2012/04/23 11:35:47 取號機-2-->號碼:13
2012/04/23 11:35:47 取號機-3-->號碼:15
2012/04/23 11:35:47 取號機-0-->號碼:16
2012/04/23 11:35:48 取號機-2-->號碼:17
2012/04/23 11:35:48 取號機-1-->號碼:18
2012/04/23 11:35:48 取號機-0-->號碼:19
2012/04/23 11:35:48 取號機-3-->號碼:20
3.
Thread-safe, eager singleton
同2.
同2.
4.
Thread-safe, lazy singleton
同2.
同2.
沒有留言:
張貼留言