單例模式是面向對象程序設計中比較經常使用的設計模式,一個類在程序的整個生命週期中只能產生不超過一個對象。(注意:這裏爲何說不超過一個對象,而不是有且只有一個對象。在使用懶漢式單例模式下,若是這個單例模式的類一直沒有使用的話,這個類就不會產生對象,即0個對象。若是使用了這個類,無論有多少處使用,這個類也只能產生1個對象。固然了,還有題外話:假如不使用這個類的話,爲啥要設計這個類呢?)ios
1.這個類只能有一個實例;c++
2.這個類必須自行建立這個實例;設計模式
3.這個類必須自行向整個系統提供這個實例。多線程
對於單利模式的要點,在單例模式的實現角度,包括下列三點函數
1.爲了防止使用者經過new操做(類的默認構造函數),賦值操做(類的默認重載賦值運算符)生成多個實例,這個類的默認構造函數,默認的重載賦值運算符必須設置爲私有的;測試
2.類中定義一個私有的該類的對象,類必須自行建立這個惟一的對象並本身保存;spa
3.類中須要提供一個公共的靜態函數,可以給使用者獲取到類本身保存的這個惟一對象,從而可以獲取到這個類提供的其餘真正提供具體服務的方法。線程
1.餓漢方式設計
在類被加載的時候,就直接初始化一個該類的對象。後面無論這個類有沒有被使用,對象都一直存在。指針
2.懶漢方式
在類真正在使用的時候,纔會初始化一個該類的對象。在沒有使用這個類以前,這個對象都不在。
頭文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 class CMySingleton 5 { 6 public: 7 //提供一個獲取該實例的方法 8 static CMySingleton& getInstance(); 9 //真正提供服務的具體方法 10 void invoke1(); 11 12 private: 13 //防止經過默認構造函數建立新對象 14 CMySingleton(); 15 //默認析構函數 16 ~CMySingleton(); 17 //防止經過拷貝構造函數和賦值運算符建立新對象 18 CMySingleton(const CMySingleton&); 19 CMySingleton& operator=(const CMySingleton&); 20 private: 21 static CMySingleton m_pInstance; 22 }; 23 24 #endif // !__SINGLETON_H__
實現文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton CMySingleton::m_pInstance;//static 成員須要類外定義 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 } 13 14 CMySingleton& CMySingleton::getInstance() 15 { 16 return m_pInstance; 17 } 18 19 void CMySingleton::invoke1() 20 { 21 std::cout << "call invoke1" << std::endl; 22 }
測試文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //調用單例類的具體方法 9 CMySingleton::getInstance().invoke1(); 10 11 return 0; 12 }
頭文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 class CMySingleton 5 { 6 public: 7 //提供一個獲取該實例的方法 8 static CMySingleton* getInstance(); 9 //真正提供服務的具體方法 10 void invoke1(); 11 12 private: 13 //防止經過默認構造函數建立新對象 14 CMySingleton(); 15 //默認析構函數 16 ~CMySingleton(); 17 private: 18 static CMySingleton* m_pInstance; 19 }; 20 21 #endif // !__SINGLETON_H__
實現文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton* CMySingleton::m_pInstance = new CMySingleton();//static 成員須要類外定義,直接建立靜態指針對象 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 if (m_pInstance) 13 { 14 delete m_pInstance; 15 m_pInstance = nullptr; 16 } 17 } 18 19 CMySingleton* CMySingleton::getInstance() 20 { 21 return m_pInstance; 22 } 23 24 void CMySingleton::invoke1() 25 { 26 std::cout << "call invoke1" << std::endl; 27 }
測試文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //調用單例類的具體方法 9 CMySingleton::getInstance()->invoke1(); 10 11 return 0; 12 }
運行結果
我的理解:靜態指針版本不須要把聲明私有的拷貝構造函數和重載賦值運算符,由於對與這兩種操做,在指針的操做後最終拿到的仍是同一個對象的地址。
單例類的對象在真正使用的時候纔去建立,簡單的實現以下
頭文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 #include <iostream> 5 6 class CMySingleton 7 { 8 public: 9 //提供一個獲取該實例的方法 10 static CMySingleton* getInstance(); 11 //真正提供服務的具體方法 12 void invoke1() 13 { 14 std::cout << "call invoke1" << std::endl; 15 } 16 17 private: 18 //防止經過默認構造函數建立新對象 19 CMySingleton() 20 //默認析構函數 21 ~CMySingleton() 22 private: 23 static CMySingleton* m_pInstance; 24 }; 25 26 #endif // !__SINGLETON_H__
實現文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成員須要類外定義,定義的時候不建立對象,在真正使用的時候才建立 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 if (m_pInstance) 13 { 14 delete m_pInstance; 15 m_pInstance = nullptr; 16 } 17 } 18 19 CMySingleton* CMySingleton::getInstance() 20 { 21 if (m_pInstance == nullptr) 22 { 23 m_pInstance = new CMySingleton(); 24 } 25 return m_pInstance; 26 } 27 28 void CMySingleton::invoke1() 29 { 30 std::cout << "call invoke1" << std::endl; 31 }
測試文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //調用單例類的具體方法 9 CMySingleton::getInstance()->invoke1(); 10 11 return 0; 12 }
運行結果與餓漢式同樣。
在單線程狀況下運行程序執行沒有問題,可是在多線程環境下,在未建立實例的時候若是同時有多個進程請求這個類的服務,因爲m_pInstance == nullptr爲True,將會致使同時建立多個實例。所以在建立實例的時候加鎖判斷實例是否已經建立,若是未建立才須要new。
頭文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 #include <iostream> 5 #include <pthread.h> 6 7 class CMySingleton 8 { 9 public: 10 //提供一個獲取該實例的方法 11 static CMySingleton* getInstance(); 12 //真正提供服務的具體方法 13 void invoke1() 14 { 15 std::cout << "call invoke1" << std::endl; 16 } 17 18 private: 19 //防止經過默認構造函數建立新對象 20 CMySingleton(); 21 //默認析構函數 22 ~CMySingleton(); 23 private: 24 static CMySingleton* m_pInstance; 25 static pthread_mutex_t m_mutex; 26 }; 27 28 class AutoLock 29 { 30 public: 31 AutoLock(pthread_mutex_t* mutex): m_mutex(mutex) 32 { 33 pthread_mutex_lock(m_mutex); 34 } 35 ~AutoLock() 36 { 37 pthread_mutex_unlock(m_mutex); 38 } 39 40 private: 41 pthread_mutex_t* m_mutex; 42 43 }; 44 45 #endif // !__SINGLETON_H__
此處使用了pthread的互斥量來做鎖操做,同時定義了一個AutoLock的類用來加鎖(此類不是必須,只要在實現裏面可以在判斷前正確加鎖便可)。
實現文件
1 #include "singleton.h" 2 #include <pthread.h> 3 4 CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成員須要類外定義,定義的時候不建立對象,在真正使用的時候才建立 5 pthread_mutex_t CMySingleton::m_mutex = PTHREAD_MUTEX_INITIALIZER; 6 7 CMySingleton::CMySingleton() 8 { 9 } 10 11 CMySingleton::~CMySingleton() 12 { 13 pthread_mutex_destroy(&m_mutex); 14 if (m_pInstance) 15 { 16 delete m_pInstance; 17 m_pInstance = nullptr; 18 } 19 } 20 21 CMySingleton* CMySingleton::getInstance() 22 { 23 AutoLock lock(&m_mutex); 24 if (m_pInstance == nullptr) 25 { 26 m_pInstance = new CMySingleton(); 27 } 28 return m_pInstance; 29 }
測試文件和測試結果跟上面同樣。
此時,因爲判斷對象是否存在前已經加鎖了,所以多線程狀況下也能正常執行了。
可是引入了另一個問題,每次在客戶端的線程(或者進程)調用的時候都要加鎖,致使效率不高。爲了解決這個問題,下面對這個實現進行改進。
頭文件不變,測試文件不變,實現文件以下
#include "singleton.h" CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成員須要類外定義,定義的時候不建立對象,在真正使用的時候才建立 pthread_mutex_t CMySingleton::m_mutex = PTHREAD_MUTEX_INITIALIZER; CMySingleton::CMySingleton() { } CMySingleton::~CMySingleton() { pthread_mutex_destroy(&m_mutex); if (m_pInstance) { delete m_pInstance; m_pInstance = nullptr; } } CMySingleton* CMySingleton::getInstance() { if (m_pInstance == nullptr) { AutoLock lock(&m_mutex); if (m_pInstance == nullptr) { m_pInstance = new CMySingleton(); } } return m_pInstance; }
此處在加鎖前多一次判斷實例是否存在(雙重斷定),所以,在第一次即便多個線程(或者進程)進來的時候第一次判斷都是空,此時多個線程調用都會競爭鎖資源,獲取到說資源的調用會建立第一個實例並返回後,以前競爭鎖資源的線程或者進程再判斷,實例已經建立,所以不會再從新new。第一波的處理能夠比較完美的解決了。
後續繼續有客戶端的請求進來,因爲再第一層判斷實例已經存在,即m_pInstance != nullptr,所以也不會進入if語句中執行加鎖操做,直接就把現有的實例返回了,從然後續的請求也能比較完美的解決了。
綜上所述,使用雙重斷定的懶漢方式僅會在沒有生成對象的第一波請求的時候纔會效率較低,後續全部的請求因爲都不存在加鎖等操做,效率也能提上來了。