單例模式是一種最為常見的軟件設(shè)計模式金句。單例模式要求:單例對象所在的類必須保證只能創(chuàng)建一個對象蛾娶。單例模式在我們?nèi)粘I詈蛙浖_發(fā)中的應(yīng)用比比皆是碾褂,比如:windows系統(tǒng)只有一個任務(wù)管理器榆俺,一個市只有一個市長骑歹。
如何保證一個類最多只能創(chuàng)建一個對象呢预烙?這個問題不能交由使用者去做處理,比如用全局變量道媚。而應(yīng)該由這個類的創(chuàng)建者在實現(xiàn)該類的時候考慮問題的解決扁掸。
單例模式巧妙的使用C++成員權(quán)限,將構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)隱藏起來(private)最域,從而有效限定使用中對對象的自由創(chuàng)建谴分。然后開放一個(static)接口,通過靜態(tài)方法創(chuàng)建對象镀脂,并在靜態(tài)方法中限定對象的唯一創(chuàng)建牺蹄。
單例模式的創(chuàng)建方式一般有兩種:懶漢式和餓漢式。
1.懶漢模式
懶漢:顧名思義薄翅,不到萬不得已該類不會去實例化對象沙兰。將對象的示例推遲到需要該對象的時候。
// 單例模式之懶漢模式
class Singleton{
public:
static Singleton* createSingleton(){ // static方法
if( m_handler == nullptr ){
m_handler = new Singleton();
}
return m_handler;
}
private:
Singleton(); //? 私有化構(gòu)造函數(shù)
~Singleton(); //? 私有化析構(gòu)函數(shù)
Singleton( const Singleton & ); //? 私有化拷貝構(gòu)造函數(shù)翘魄,防止通過拷貝構(gòu)造復(fù)制對象
static Singleton* m_handler;
};
Singleton* Singleton::m_handler = nullptr;
int main()
{
Singleton *ptr1 = Singleton::createSingleton();
Singleton *ptr2 = ptr1-> createSingleton();? // ptr1 和 ptr2 指向同一個對象
return 0;
}
2.餓漢模式
餓漢:單例類在創(chuàng)建類的時候就創(chuàng)建了對象鼎天。
// 單例模式之餓漢模式
class Singleton{
public:
static Singleton* getSingleton(){
return m_handler;
}
private:
Singleton(); //? 私有化構(gòu)造函數(shù)
~Singleton(); //? 私有化析構(gòu)函數(shù)
Singleton( const Singleton & ); //? 私有化拷貝構(gòu)造函數(shù),防止通過拷貝構(gòu)造復(fù)制對象
static Singleton* m_handler;
};
Singleton* Singleton::m_handler = new Singleton; // 類創(chuàng)建時暑竟,創(chuàng)建對象
int main()
{
Singleton *ptr1 = Singleton::createSingleton();
Singleton *ptr2 = ptr1-> createSingleton();? // ptr1 和 ptr2 指向同一個對象
return 0;
}
3.單例模式中的線程安全
前面我們考慮了單例模式的懶漢式和餓漢式斋射,但是我們只考慮了普通單線程情況。如果考慮到多線程情況,那么上面的懶漢模式則不是線程安全的罗岖。而餓漢模式因為在編譯階段已經(jīng)創(chuàng)建了對象涧至,所有它是線程安全的。
如何解決懶漢模式的線程不安全呢桑包?通常情況我們可以通過互斥鎖解決臨界資源的訪問問題南蓬。
// 單例模式之懶漢模式+線程安全
class Singleton{
public:
static Singleton* createSingleton(){ // static方法
if( m_handler == nullptr ){? // 解決訪問效率問題
pthread_mutex_lock( &m_lock );
if( m_handler == nullptr ){
m_handler = new Singleton();
}
pthread_mutex_unlock( &m_lock );
return m_handler;
}
}
private:
Singleton(); //? 私有化構(gòu)造函數(shù)
~Singleton(); //? 私有化析構(gòu)函數(shù)
Singleton( const Singleton & ); //? 私有化拷貝構(gòu)造函數(shù),防止通過拷貝構(gòu)造復(fù)制對象
static Singleton* m_handler;
static pthread_mutex_t? m_lock;
};
Singleton* Singleton::m_handler = nullptr;
pthread_mutex_t? Singleton::m_lock = PTHREAD_MUTEX_INITIALIZER;
int main()
{
Singleton *ptr1 = Singleton::createSingleton();
Singleton *ptr2 = ptr1-> createSingleton();? // ptr1 和 ptr2 指向同一個對象
return 0;
}
上面例程通過互斥鎖捡多,看似解決了多線程中的臨界資源互斥問題蓖康。但是實際上并非如此铐炫。具體問題如下:
上面代碼中:m_handler = new Singleton();? 我們期望的執(zhí)行順序是:
(1)分配一段內(nèi)存? (2)構(gòu)造對象垒手,放入內(nèi)存? (3)m_handler存內(nèi)存地址
但是實際執(zhí)行可能是:
(1)分配一段內(nèi)存? (2) m_handler存內(nèi)存地址? (3)構(gòu)造對象,放入內(nèi)存
那么后面的情況可能導(dǎo)致倒信,對象還沒創(chuàng)建科贬,但是已經(jīng)被另外一個線程拿去使用了,這種情況可能導(dǎo)致嚴(yán)重錯誤鳖悠。那么如何解決呢榜掌?大家可以思考一下。
文章來源:學(xué)到牛牛 www.xuedaon.com