單例模式:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
它是一種常見的面向對象的設計模式,屬於創建型模式。可以保證應用單例模式的類只有一個對象實例。
優點: 1、在內存裏只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷燬實例(比如管理學院首頁頁面緩存)。 2、避免對資源的多重佔用(比如寫文件操作)。
缺點:沒有接口,不能繼承
應用實例:
1、Windows 是多進程多線程的,在操作一個文件的時候,就不可避免地出現多個進程或線程同時操作一個文件的現象,所以所有文件的處理必須通過唯一的實例來進行。
2、一些設備管理器常常設計爲單例模式,比如一個電腦有兩臺打印機,在輸出的時候就要處理不能兩臺打印機打印同一個文件。
3、WEB 中的計數器,不用每次刷新都在數據庫里加一次,用單例先緩存起來。
4、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。因此有時確保系統中某個對象的唯一性即一個類只能有一個實例非常重要。
和java一樣,C++也有懶加載和快加載之分。
快加載:(只有在編譯類時實例化對象)
class SingleTon
{
public:
static SingleTon* getInstance()
{
return &singleTon;
}
private:
SingleTon(){}
static SingleTon singleTon;
};
SingleTon SingleTon::singleTon;
這是線程安全的,因爲這是在編譯鏈接時完成的,程序的所有執行序列還沒有開始執行呢。
慢加載:只有使用時才實例化對象。
①線程不安全的函數。
class SingleTon
{
public:
static SingleTon* getInstance()
{
if(single == NULL)
{
single = new SingleTon();
}
return single;
}
private:
Single(){}
static SingleTon* single;
}
SingleTon * SingleTon::single = NULL;
因爲判斷函數是否是線程安全的,就看是否存在競態條件。判斷是否存在競態條件,是看線程調度順序的不同,代碼執行結果是否一樣。而存在競態條件的代碼稱爲臨界區,臨界區是需要原子操作的,if語句不是,互斥鎖是原子操作。
②線程安全的函數(可重入函數)
class SingleTon
{
public:
static SingleTon* getInstance()
{
pthread_mutex_lock(&mutex);
if(single == NULL)
{
single = new SingleTon();
}
pthread_mutex_unlock(&mutex);
return single;
}
private:
Single(){}
~Single(){pthread_mutex_destroy(&mutex, NULL);}
static pthread_mutex_t mutex;
static SingleTon* single;
}
SingleTon * SingleTon::single = NULL;
if語句前後加了互斥鎖保證原子操作。。雖然保證了線程安全,可是有個問題隨之出現。我們只要調用此函數每次都會執行加鎖和解鎖,無論是否以實例化對象了。而互斥量加鎖解鎖操作均需要進行兩次上下文切換,會耗費好些資源和時間。所以應該避免每次的加解鎖問題。
③優化了的線程安全函數
class SingleTon
{
public:
static SingleTon* getInstance()
{
if(single == NULL){
pthread_mutex_lock(&mutex);
if(single == NULL)
{
single = new SingleTon();
}
pthread_mutex_unlock(&mutex);
return single;
}
}
private:
Single(){}
~Single(){pthread_mutex_destroy(&mutex, NULL);}
static pthread_mutex_t mutex;
static SingleTon* single;
}
SingleTon * SingleTon::single = NULL;
加入if語句後可以減少部分對互斥量的多餘訪問。因爲還存在一種可能,當線程1和2 很快進入第一個if語句,並且沒有進入第二個if語句時,這時1、2線程再繼續執行,還是會對互斥量進行訪問。。
所以說這種基於”懶加載”雙重if的線程安全函數也不是完美解決方案,倒是“快加載”不會出現這些問題....