1 教科書里的單例模式
我們都很清楚一個簡單的單例模式該怎樣去實現:構造函數聲明為private或protect防止被外部函數實例化,內部保存一個private static的類指針保存唯一的實例,實例的動作由一個public的類方法代勞,該方法也返回單例類唯一的實例。
上代碼:
class singleton { protected: singleton(){} private: static singleton* p; public: static singleton* instance(); }; singleton* singleton::p = NULL; singleton* singleton::instance() { if (p == NULL) p = new singleton(); return p; }
這是一個很棒的實現,簡單易懂。但這是一個完美的實現嗎?不!該方法是線程不安全的,考慮兩個線程同時首次調用instance方法且同時檢測到p是NULL值,則兩個線程會同時構造一個實例給p,這是嚴重的錯誤!同時,這也不是單例的唯一實現!
一、懶漢模式:即第一次調用該類實例的時候才產生一個新的該類實例,並在以后僅返回此實例。
需要用鎖,來保證其線程安全性:原因:多個線程可能進入判斷是否已經存在實例的if語句,從而non thread safety.
使用double-check來保證thread safety.但是如果處理大量數據時,該鎖才成為嚴重的性能瓶頸。
1、靜態成員實例的懶漢模式:
class Singleton { private: static Singleton* m_instance; Singleton(){} public: static Singleton* getInstance(); }; Singleton* Singleton::getInstance() { if(NULL == m_instance) { Lock();//借用其它類來實現,如boost if(NULL == m_instance) { m_instance = new Singleton; } UnLock(); } return m_instance; }
2、內部靜態實例的懶漢模式
這里需要注意的是,C++0X以后,要求編譯器保證內部靜態變量的線程安全性,可以不加鎖。但C++ 0X以前,仍需要加鎖。
class SingletonInside { private: SingletonInside(){} public: static SingletonInside* getInstance() { Lock(); // not needed after C++0x static SingletonInside instance; UnLock(); // not needed after C++0x return &instance; } };
使用實例:
1 #include<iostream> 2 using namespace std; 3 class CSingleton 4 { 5 private: 6 CSingleton() { 7 } 8 ~CSingleton() { 9 if (m_pInstance == NULL) { 10 return; 11 } 12 delete m_pInstance; 13 m_pInstance = NULL; 14 } 15 static CSingleton *m_pInstance; 16 public: 17 static CSingleton * GetInstance() { 18 if(m_pInstance == NULL) 19 m_pInstance = new CSingleton(); 20 return m_pInstance; 21 } 22 }; 23 CSingleton* CSingleton::m_pInstance = NULL;//類的靜態成員變量需要在類外邊初始化 24 25 26 class SingletonInside 27 { 28 private: 29 SingletonInside(){} 30 public: 31 static SingletonInside* getInstance() 32 { 33 static SingletonInside instance; 34 return &instance; 35 } 36 void init(){ 37 m_data = 10; 38 } 39 void set(){ 40 ++m_data; 41 } 42 int m_data; 43 }; 44 45 int main() { 46 47 SingletonInside* single1 = SingletonInside::getInstance(); 48 SingletonInside* single2 = SingletonInside::getInstance(); 49 50 single1->set(); 51 if (single1 == single2) { 52 cout<<"Same"<<endl; 53 single1->init(); 54 single1->set(); 55 cout<<single2->m_data <<endl; 56 }else{ 57 cout<<"not =="<<endl; 58 } 59 return 0; 60 }
output:
Same 11
二、餓漢模式:即無論是否調用該類的實例,在程序開始時就會產生一個該類的實例,並在以后僅返回此實例。
由靜態初始化實例保證其線程安全性,WHY?因為靜態實例初始化在程序開始時進入主函數之前就由主線程以單線程方式完成了初始化,不必擔心多線程問題。
故在性能需求較高時,應使用這種模式,避免頻繁的鎖爭奪。
class SingletonStatic { private: static const SingletonStatic* m_instance; SingletonStatic(){} public: static const SingletonStatic* getInstance() { return m_instance; } }; //外部初始化 before invoke main const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;
特點與選擇:
由於要進行線程同步,所以在訪問量比較大,或者可能訪問的線程比較多時,采用餓漢實現,可以實現更好的性能。這是以空間換時間。
在訪問量較小時,采用懶漢實現。這是以時間換空間。