C++ 單例模式(懶漢、餓漢模式)


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的操作。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM