1、簡單的單例模式實現
2、C++的構造函數不是線程安全的,所以上述代碼在多線程的情況下是不安全的,原因是new Singelton時,這句話不是原子的,比如一個線程執行了new的同時,另一個線程對if進行判斷(此時實例還沒被創建出來)。在windows下模擬:
#include <iostream> #include <process.h> #include <windows.h> using namespace std; class Singelton{ private: Singelton(){ m_count ++; printf("Singelton begin\n"); Sleep(1000); // 加sleep為了放大效果 printf("Singelton end\n"); } static Singelton *single; public: static Singelton *GetSingelton(); static void print(); static int m_count; }; Singelton *Singelton::single = nullptr; int Singelton::m_count = 0; Singelton *Singelton::GetSingelton(){ if(single == nullptr){ single = new Singelton; } return single; } void Singelton::print(){ cout<<m_count<<endl; } // 回調函數 void threadFunc(void *p){ DWORD id = GetCurrentThreadId(); // 獲得線程id cout<<id<<endl; Singelton::GetSingelton()->print(); // 構造函數並獲得實例,調用靜態成員函數 } int main(int argc, const char * argv[]) { int threadNum = 3; HANDLE threadHdl[100]; // 創建3個線程 for(int i = 0; i<threadNum; i++){ threadHdl[i] = (HANDLE)_beginthread(threadFunc, 0, nullptr); } // 讓主進程等待所有的線程結束后再退出 for(int i = 0; i<threadNum; i++){ WaitForSingleObject(threadHdl[i], INFINITE); } cout<<"main"<<endl; // 驗證主進程是否是最后退出 return 0; }
運行結果:
該單例模式也稱為懶漢式單例。
懶漢:故名思義,不到萬不得已就不會去實例化類,也就是說在第一次用到類實例的時候才會去實例化。與之對應的是餓漢式單例。(注意,懶漢本身是線程不安全的,如上例子)
餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行實例化。(本身就是線程安全的,如下例子)
關於如何選擇懶漢和餓漢模式:
特點與選擇:
懶漢:在訪問量較小時,采用懶漢實現。這是以時間換空間。
餓漢:由於要進行線程同步,所以在訪問量比較大,或者可能訪問的線程比較多時,采用餓漢實現,可以實現更好的性能。這是以空間換時間。
3、餓漢式的單例實現
#include <iostream> #include <process.h> #include <windows.h> using namespace std; class Singelton{ private: Singelton(){ m_count ++; printf("Singelton begin\n"); Sleep(1000); // 加sleep為了放大效果 printf("Singelton end\n"); } static Singelton *single; public: static Singelton *GetSingelton(); static void print(); static int m_count; }; // 餓漢模式的關鍵:初始化即實例化 Singelton *Singelton::single = new Singelton; int Singelton::m_count = 0; Singelton *Singelton::GetSingelton(){ // 不再需要進行實例化 //if(single == nullptr){ // single = new Singelton; //} return single; } void Singelton::print(){ cout<<m_count<<endl; } // 回調函數 void threadFunc(void *p){ DWORD id = GetCurrentThreadId(); // 獲得線程id cout<<id<<endl; Singelton::GetSingelton()->print(); // 構造函數並獲得實例,調用靜態成員函數 } int main(int argc, const char * argv[]) { int threadNum = 3; HANDLE threadHdl[100]; // 創建3個線程 for(int i = 0; i<threadNum; i++){ threadHdl[i] = (HANDLE)_beginthread(threadFunc, 0, nullptr); } // 讓主進程等待所有的線程結束后再退出 for(int i = 0; i<threadNum; i++){ WaitForSingleObject(threadHdl[i], INFINITE); } cout<<"main"<<endl; // 驗證主進程是否是最后退出 return 0; }
運行結果:
4、線程安全的懶漢式單例的實現
餓漢式會提前浪費我們的內存空間以及資源,如果有項目中要求我們在使用到實例的時候再去實例化,則還是需要使用懶漢式。
class singleton { protected: singleton() { // 初始化 pthread_mutex_init(&mutex); } private: static singleton* p; public: static pthread_mutex_t mutex; static singleton* initance(); }; pthread_mutex_t singleton::mutex; singleton* singleton::p = NULL; singleton* singleton::initance() { if (p == NULL) { // 加鎖 pthread_mutex_lock(&mutex); if (p == NULL) p = new singleton(); pthread_mutex_unlock(&mutex); } return p; }
需要注意的是:上面進行的兩次if(p == NULL)的檢查,因為當獲得了實例之后,有了外層的判斷之后,就不會再進入到內層判斷,即不會再進行lock以及unlock的操作。