經典的Singleton模式可以爲我們提供一個簡單的解決方案,他提供了一個全局訪問點,用來訪問這個唯一實例,簡單思想是:當外界想要持有這個類型的實例時,這個類型會自己檢查這個實例是否已經被創建,如果沒有則創建一個並返回這個實例的指針/引用,有了,則簡單的返回這個對象的指針/引用即可。而且爲了確保該類型的實例的唯一性,將構造函數protected或private。
單件模式的示例代碼如下:
...{
public:
static Singleton* GetInstance();
protected:
Singleton();
private:
static Singleton* m_pInstance;
};
Singleton* Singleton::m_pInstance = 0;
Singleton* Singleton::GetInstance()
...{
if(m_pInstance == 0)
...{
m_pInstance = new Singleton;
}
return m_pInstance;
}
單件模式還可以與工廠模式連用,即可以在GetInstance中判斷一下需要的到底是那一種單件,然後用一個子類的對象返回給一個基類的指針就可以了。不過這些場模式的實例必須遵循單件模式。
當然原型模式也可以連用,我之前有一篇blog介紹了我學mfc的時候做的一個簡單的畫圖程序,當時用到了原型模式和場模式,裏面的畫筆和畫刷其實可以做成單件模式,這樣就不必在用戶每次切換畫筆或畫刷的時候new一個pen或者brush了。
上面說的情況沒有考慮到線程問題,即如果存在多個線程要訪問我們這個類的那個實例,就會有這樣、那樣的問題:
1.如果第一個線程訪問這個類時,m_pInstance == 0爲true,那麼他要new一個Singleton的對象,恰巧第二個線程也訪問這個類,這是還沒有new 這個類的對象,而且m_pInstance也是空,那麼他也會new一個Singleton的對象,這就有問題,明明創建了兩個對象麼!析構時怎麼辦?只能內存泄露吧?
2.由於對象是唯一的,如果這個Singleton有一個方法是取得一組值,另一個方法是遍歷這組值,不加鎖的情況下,可能會在第一個線程遍歷到那組值的中間的時候,另一個線程開始遍歷,在這樣比較背的情況下,你往下遍歷一個,我往下遍歷一個,就亂套了~
......
解決辦法就是在m_pInstance上加鎖,那麼類似第二種情況的問題就可以解決,但是第一種情況呢?我們需要做的可能就是對“檢查Singleton對象已經被創建”進行同步。這麼做挺簡單,不過會造成一個性能上的瓶頸,所有線程都必需等待檢查對象是否存在。也許這個對象已經早在800年前就創建了,不過還得鎖上-檢查-解鎖,浪費資源。另一種做法就是被稱爲雙重檢查-鎖定的模式Doublechecked Locking!其意圖就是將上面的那些已經存在對象的情況的鎖上-檢查-解鎖的步驟省略掉。因此不會造成系統的瓶頸,問題只出現在第一次創建對象的時候,那麼就把他提取出來好了。示例代碼如下:
...{
if(m_pInstance == 0)
...{
//在這裏同步,加鎖
if(m_pInstance == 0)
...{
m_pInstance = new Singleton;
}
}
return m_pInstance;
}