2009年4月17日 星期五

Design Pattern: Singleton 的型式

Singleton 是非常經典的 Design Pattern。最簡單,也是最具實用性的 pattern。
我也最常拿來考面試者。一個有經驗的 programmer,怎麼可能沒碰過 Singleton?

目的

全域只有一個實例(instance)

舉例

一家銀行的分行,通常會有多個櫃台(窗口),以加快處理速度。但只能有一個取號機,避免號碼牌重覆。

因此,該取號機只能有一台。如果有兩台,天下就大亂了。

程式在實作時,也常常有類似的需求。如何才能確保一個 instance呢?大家經驗累積的結果,都有類似的樣式(pattern)。這樣的 pattern 後來被取名為 singleton,以方便大家的溝通。

策略

  1. 將預設建構子封閉起來,不讓外界直接建立 instance.
  2. 使用靜態屬性(static property),讓外界取的唯一的 instance

最常見的實作如下。

實作1
class MySingleton
{
private static MySingleton _singleton = null;
private int currentNumber = 0;
private MySingleton()
{
}

public static MySingleton Current
{
get
{
if (_singleton == null)//注意這一行
_singleton = new MySingleton();
return _singleton;
}
}

public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}


Client


而Client code 的使用方式如下
MySingleton.Current.WriteNumber();
MySingleton.Current.WriteNumber();
MySingleton.Current.WriteNumber();

呼叫的結果,就是 1, 2, 3了。

實作1的方法並不完美,因為不是 thread-safe。換句話說,使用在 multi-thread 的環境下,是有可能還是建立了多個 instance。可能發生問題的程式碼,就是實作1內註解的「注意這一行」。

因此,有人改寫如下


實作2: thread-safe


class MySingleton
{
private static MySingleton _singleton = null;
private int currentNumber = 0;
private MySingleton()
{
}

public static MySingleton Current
{
get
{
if (_singleton == null)
{
lock(typeof(MySingleton))
_singleton = new MySingleton();
}
return _singleton;
}
}

public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}

實作2的方法,是使用了 lock 的 c# 語法,讓進入 lock區段的執行緒只能有一個。

實作2其實已經相當完美了。唯一美中不足的是,簡單的singleton,竟然要這麼複雜?因此,又有人改寫如下

實作3: thread-safe, eager singleton


class MySingleton
{
private static MySingleton _singleton = new MySingleton();
private int currentNumber = 0;
private MySingleton()
{
}

public static MySingleton Current
{
get
{
return _singleton;
}
}

public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}

實作3是使用了 static 的 instance,並在一開始就建立了 instance。即使用了private static MySingleton _singleton = new MySingleton();這一行。


這樣的好處,是大幅減少了程式碼的複雜度,相當容易維護。我相當推薦這一型。

實作3有一個壞處,當 Client 尚未需要 singleton的 instance時,程式就先建立了 instance。而實作2時會先判斷是否尚未建立實例。

if (_singleton == null)

因此,實作2是lazy singleton「要用時才建立instance」, 但not thread-safe。而實作3是eager singleton,「未用時已建立 instance」, thread-safe。

有沒有既是 thread-safe, 又是 lazy singleton 的寫法呢?因此產生 實作4

實作4: thread-safe, lazy singleton



class MySingleton
{
private int currentNumber = 0;
private MySingleton() { }

public static MySingleton Current
{
get {
return Inner.Current;
}
}

public void WriteNumber() {
currentNumber++;
Console.WriteLine(currentNumber);
}

class Inner {
static Inner() { }
internal static readonly MySingleton Current = new MySingleton();
}
}

實作4非常巧妙地使用 inner class 並結合 static instance 的用法,完美地完成了任務,是我目前看過最棒的 singleton 解法。

8 則留言:

Lily 提到...

範例二
if (_singleton == null)
{
lock(typeof(MySingleton))
_singleton = new MySingleton();
}
return _singleton;

應該改成
lock(typeof(MySingleton))
{
if (_singleton == null)
_singleton = new MySingleton();
}
return _singleton;
這樣吧? 否則可能會依序重複執行多次 _singleton = new MySingleton();
(因為 if 會被衝破)

秉程 提到...

沒錯。因此,比較好的會有兩層的 lock
lock(typeof(MySingleton))
{
if (_singleton == null)
lock(typeOf(MySingleton)) {
_singleton = new MySingleton();
}
}
這樣子才能防的比較正確。但可維護性又更差了。

Lily 提到...

請問為什麼要做兩層@@?
第一層不就已經 lock 住了嗎?
再呼叫一次會有差嗎?

秉程 提到...

正如你說的啊 (因為 if 會被衝破)

Lily 提到...

第一個 lock 可以防止 if 被衝破
第二個 lock 的作用是??

秉程 提到...

查一下 singleton double lock

https://msdn.microsoft.com/zh-tw/library/ff650316.aspx

秉程 提到...

應該是 double check ,使用 if 就夠了

Lily 提到...

"Double-Checked Locking is Broken"
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

那個... double check 壞掉了.... 囧

Share with Facebook