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

引出問題:

設計一個類,我們只能生成該類的一個對象實例。

不好的解法:只適用於單線程環境

因爲該類只能生成一個對象實例,那麼該類的構造函數必須是私有的,從而避免他人創建實例。在需要的時候創建該類的一個對象。

下面是程序實現:

/*************************************************************************
	> 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函數開始執行之前,類實例已經創建完了。


更多詳情,請前往本人博客網站:個人博客網站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章