設計模式——單例模式C++實現

readyao發表於2016-06-18

引出問題:

設計一個類,我們只能生成該類的一個物件例項。

不好的解法:只適用於單執行緒環境

因為該類只能生成一個物件例項,那麼該類的建構函式必須是私有的,從而避免他人建立例項。在需要的時候建立該類的一個物件。

下面是程式實現:

/*************************************************************************
	> File Name: SigleInstance.cpp
	> Author: 
	> Mail: 
	> Created Time: Sat 18 Jun 2016 06:44:44 PM CST
 ************************************************************************/

#include <iostream>
using namespace std;


//只能生成一個類物件的類
class SigleInstance
{
public:
    //指向類的一個例項物件
    static SigleInstance * instance;
private:
    //私有的建構函式
    SigleInstance(){cout << "執行SigleInstance 建構函式" << endl;}
public:
    //建立一個例項物件
    static SigleInstance * getSigleInstance(){
        if(instance == NULL)
            instance = new SigleInstance();

        return instance;
    }
};


//初始化靜態成員變數
SigleInstance * SigleInstance::instance = NULL;

//測試函式
int main()
{

    SigleInstance * SigleInstance1 = SigleInstance::getSigleInstance();
    SigleInstance * SigleInstance2 = SigleInstance::getSigleInstance();
    SigleInstance * SigleInstance3 = SigleInstance::getSigleInstance();


    if(SigleInstance1 == SigleInstance2){
        cout << "SigleInstance1 == SigleInstance2" << endl;
    }

    if(SigleInstance1 == SigleInstance3){
        cout << "SigleInstance1 == SigleInstance3" << endl;
    }

    return 0;
}

下面是程式的輸出:
執行SigleInstance 建構函式
SigleInstance1 == SigleInstance2
SigleInstance1 == SigleInstance3
從程式的輸出可以看出,SigleInstance1和SigleInstance2和SigleInstance3指向的都是同一個物件。

不好的解法2:可以在多執行緒環境下工作,但是效率比較低

解法1中的程式碼在單執行緒的情況下工作正常,但在多執行緒的環境下就有問題了。如果兩個執行緒同時執行判斷instance是否為NULL的if語句,並且instance例項還沒有建立時,那麼兩個執行緒都會建立一個例項,那麼此時就不滿足單例模式的要求了。

為了保證在多執行緒的環境下我們還是隻能得到一個例項,需要加上一個同步鎖。

下面是實現的程式:

/*************************************************************************
	> File Name: SigleInstance.cpp
	> Author: 
	> Mail: 
	> Created Time: Sat 18 Jun 2016 06:44:44 PM CST
 ************************************************************************/

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;


//只能生成一個類物件的類
class SigleInstance
{
public:
    //指向類的一個例項物件
    static SigleInstance * instance;

    //定義一個互斥鎖變數
    static pthread_mutex_t instanceMutex;

private:
    //私有的建構函式
    SigleInstance(){cout << "執行SigleInstance 建構函式" << endl;}
public:
    //建立一個例項物件
    static SigleInstance * getSigleInstance(){
        //獲得互斥鎖
        pthread_mutex_lock(&instanceMutex);

        if(instance == NULL)
            instance = new SigleInstance();

        //睡眠僅僅為了測試其它執行緒是否處於等待狀態,真正的程式是不需要該延遲的
        sleep(3);

        //釋放互斥鎖
        pthread_mutex_unlock(&instanceMutex);

        return instance;
    }
};


//初始化靜態成員變數
SigleInstance * SigleInstance::instance = NULL;
//初始化互斥鎖變數
pthread_mutex_t SigleInstance::instanceMutex = PTHREAD_MUTEX_INITIALIZER;


//執行緒要執行的函式
void * threadFun(void *arg)
{
    SigleInstance * instance = SigleInstance::getSigleInstance();
    cout << "theread" << dec << pthread_self() << ", instance's address = " << hex << instance << endl;

    pthread_exit(NULL);
}

//測試函式
int main()
{
    const int NUM = 3;
    pthread_t threadId[NUM];

    //建立NUM個執行緒
    for(int i = 0; i < NUM; ++i){
        pthread_create(&threadId[i], NULL, threadFun, NULL);
    }

    for(int i = 0; i < NUM; ++i){
        pthread_join(threadId[i], NULL);
    }

    return 0;
}

下面是程式的輸出:

執行SigleInstance 建構函式
theread139706800436992, instance's address = 0x7f10000008c0
theread139706808829696, instance's address = 0x7f10000008c0
theread139706817222400, instance's address = 0x7f10000008c0

從輸出結果可以看出,執行了一個建構函式,每個執行緒獲得的例項物件的地址都是相同的,表明只有一個例項物件。並且每個執行緒之間的等待時間為2秒。

在上面的程式中,因為每個時刻只能有一個執行緒獲得互斥鎖,當第一個執行緒獲得互斥鎖的時候,其它執行緒只能等待。當第一個執行緒獲得互斥鎖後,發現例項還沒有建立時,它會建立一個例項。接著第一個執行緒釋放互斥鎖,此時第二個執行緒可以獲得互斥鎖,並執行下面的程式碼,但是此時已經建立了一個例項,所以,第二個執行緒直接返回,不會再建立一個例項。這樣就可以保證在多執行緒的環境下也只能建立一個例項。

但是解法2不是很完美,因為每次通過獲取例項物件的時候都會試圖獲得互斥鎖,但是加鎖和解鎖是非常耗時的操作,我們應該儘量避免加鎖和解鎖的操作。具體改進看下面解法3。

改進解法2:加鎖前後兩次判斷例項是否已經存在

下面是實現的程式:
/*************************************************************************
	> File Name: SigleInstance.cpp
	> Author: 
	> Mail: 
	> Created Time: Sat 18 Jun 2016 06:44:44 PM CST
 ************************************************************************/

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;


//只能生成一個類物件的類
class SigleInstance
{
public:
    //指向類的一個例項物件
    static SigleInstance * instance;

    //定義一個互斥鎖變數
    static pthread_mutex_t instanceMutex;

private:
    //私有的建構函式
    SigleInstance(){cout << "執行SigleInstance 建構函式" << endl;}
public:
    //建立一個例項物件
    static SigleInstance * getSigleInstance(){

        //如果例項沒有建立,則加鎖並建立例項
        //如果例項已經建立,則直接返回該例項的指標
        if(instance == NULL){
            //獲得互斥鎖
            pthread_mutex_lock(&instanceMutex);

            if(instance == NULL)
                instance = new SigleInstance();

            //睡眠僅僅為了測試其它執行緒是否處於等待狀態,真正的程式是不需要該延遲的
            sleep(3);

            //釋放互斥鎖
            pthread_mutex_unlock(&instanceMutex);
        }

        return instance;
    }
};


//初始化靜態成員變數
SigleInstance * SigleInstance::instance = NULL;
//初始化互斥鎖變數
pthread_mutex_t SigleInstance::instanceMutex = PTHREAD_MUTEX_INITIALIZER;


//執行緒要執行的函式
void * threadFun(void *arg)
{
    SigleInstance * instance = SigleInstance::getSigleInstance();
    cout << "theread" << dec << pthread_self() << ", instance's address = " << hex << instance << endl;

    pthread_exit(NULL);
}

//測試函式
int main()
{
    const int NUM = 3;
    pthread_t threadId[NUM];

    //建立NUM個執行緒
    for(int i = 0; i < NUM; ++i){
        pthread_create(&threadId[i], NULL, threadFun, NULL);
    }

    for(int i = 0; i < NUM; ++i){
        pthread_join(threadId[i], NULL);
    }

    return 0;
}

下面是程式的輸出:

執行SigleInstance 建構函式
theread139991850669824, instance's address = 0x7f525c0008c0
theread139991859062528, instance's address = 0x7f525c0008c0
theread139991842277120, instance's address = 0x7f525c0008c0

從程式的輸出情況來看,執行緒139991842277120是建立該類例項的執行緒,因為,它經過了睡眠,所以是最後輸出。另外兩個執行緒輸出比較早,線上程139991842277120建立完例項之後,另外兩個執行緒就返回了,這兩個執行緒沒有進行加鎖和解鎖操作。


解法4:利用靜態變數的初始化順序

這種方法其實是單例模式中的餓漢式方式,上面的方法是單例模式中的懶漢式方式。

這種方法的例項物件,是在類變數instance初始化的時候就建立了該例項,也就是說程式一開始執行,

參考文章:

設計模式——單例模式java實現


下面是程式實現:

/*************************************************************************
	> File Name: SigleInstance.cpp
	> Author: 
	> Mail: 
	> Created Time: Sat 18 Jun 2016 06:44:44 PM CST
 ************************************************************************/

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;


//只能生成一個類物件的類
class SigleInstance
{
public:
    //指向類的一個例項物件
    static SigleInstance * instance;

private:
    //私有的建構函式
    SigleInstance(){cout << "執行SigleInstance 建構函式" << endl;}
public:
    //建立一個例項物件
    static SigleInstance * getSigleInstance(){
        return instance;
    }
};


//初始化靜態成員變數,此時直接建立一個例項物件
SigleInstance * SigleInstance::instance = new SigleInstance();


//執行緒要執行的函式
void * threadFun(void *arg)
{
    SigleInstance * instance = SigleInstance::getSigleInstance();
    cout << "theread" << dec << pthread_self() << ", instance's address = " << hex << instance << endl;

    pthread_exit(NULL);
}

//測試函式
int main()
{
    cout << "main start..." << endl;

    const int NUM = 3;
    pthread_t threadId[NUM];

    //建立NUM個執行緒
    for(int i = 0; i < NUM; ++i){
        pthread_create(&threadId[i], NULL, threadFun, NULL);
    }

    for(int i = 0; i < NUM; ++i){
        pthread_join(threadId[i], NULL);
    }

    return 0;
}

下面是程式的輸出:

執行SigleInstance 建構函式
main start...
theread140681870264064, instance's address = 0xbaa010
theread140681878656768, instance's address = 0xbaa010
theread140681887049472, instance's address = 0xbaa010

從輸出結果可以看出,在main函式開始執行之前,類例項已經建立完了。


更多詳情,請前往本人部落格網站:個人部落格網站

相關文章