引出問題:
設計一個類,我們只能生成該類的一個對象實例。
不好的解法:只適用於單線程環境
因爲該類只能生成一個對象實例,那麼該類的構造函數必須是私有的,從而避免他人創建實例。在需要的時候創建該類的一個對象。
下面是程序實現:
/*************************************************************************
> 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初始化的時候就創建了該實例,也就是說程序一開始運行,
參考文章:
下面是程序實現:
/*************************************************************************
> 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函數開始執行之前,類實例已經創建完了。
更多詳情,請前往本人博客網站:個人博客網站