一、單例模式
一般情況下,我們建立的一些類是屬於工具性質的,基本不用存儲太多的跟自身有關的數據,在這種情況下,每次都去new一個對象,即增加了開銷,也使得代碼更加臃腫。其實,我們只需要一個實例對象就可以。如果採用全局或者靜態變量的方式,會影響封裝性,難以保證別的代碼不會對全局變量造成影響。
考慮到這些需要,我們將默認的構造函數聲明爲私有的,這樣就不會被外部所new了,甚至可以將析構函數也聲明爲私有的,這樣就只有自己能夠刪除自己了。這就是所謂的單例模式啦。
1、基本概念
數學與邏輯學中,singleton定義爲“有且僅有一個元素的集合”。
單例模式最初的定義出現於《設計模式》(艾迪生維斯理, 1994):“保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。”
通俗來講,所謂單例模式就是一個類只有一個實例
2、單例模式的特點
(1)單例類確保自己只有一個實例
(2)單例類必須自己創建自己的實例
(3)單例類必須爲其他對象提供唯一的實例
二、單例模式的幾種實現方式
1、懶漢式(線程不安全)
構造函數聲明爲private或protect防止被外部函數實例化,內部保存一個private static的類指針保存唯一的實例,實例的動作由一個public的類方法代勞,該方法也返回單例類唯一的實例。
代碼實現:
class singleton
{
protected:
singleton(){}
private:
static singleton* p;
public:
static singleton* instance();
};
singleton* singleton::p = NULL;
singleton* singleton::instance()
{
if (p == NULL)
p = new singleton();
return p;
}
這個方法實現起來,簡單易懂,但是存在一個致命的不安全因素,該方法是線程不安全的,考慮兩個線程同時首次調用instance方法且同時檢測到p是NULL值,則兩個線程會同時構造一個實例給p,這是嚴重的錯誤!所以該方法只適用於單線程方法。
2、懶漢式(線程安全)
爲了解決上面出現的問題,可以有以下兩種方法解決:
加鎖的經典懶漢模式:
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),這樣就確保不會重複創建了.
內部靜態變量的懶漢實現:
在instance函數裏定義一個靜態的實例,也可以保證擁有唯一實例,在返回時只需要返回其指針就可以了。推薦這種實現方法。
class singleton
{
protected:
singleton()
{
pthread_mutex_init(&mutex);
}
public:
static pthread_mutex_t mutex;
static singleton* initance();
};
pthread_mutex_t singleton::mutex;
singleton* singleton::initance()
{
pthread_mutex_lock(&mutex);
static singleton obj;
pthread_mutex_unlock(&mutex);
return &obj;
}
3、餓漢式(以空間換時間)
餓漢:餓了肯定要飢不擇食,所以在單例類定義的時候就進行實例化。
代碼實現如下:
class singleton
{
protected:
singleton()
{}
private:
static singleton* p;
public:
static singleton* initance();
};
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}
那麼針對以上方式如何選擇呢:
- 在單線程中,不存在線程安全時,可以採用懶漢式(線程不安全)
- 由於要進行線程同步,所以在訪問量比較大,或者可能訪問的線程比較多時,採用餓漢實現,可以實現更好的性能。這是以空間換時間。
- 在訪問量較小時,採用懶漢(線程安全)實現。這是以時間換空間。
三、單例模式的主要優缺點
1、優點
1、提供了對唯一實例的受控訪問。
2、由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象單例模式無疑可以提高系統的性能。
3、允許可變數目的實例。
2、缺點
1、由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
2、單例類的職責過重,在一定程度上違背了“單一職責原則”。
3、濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。