c語言設(shè)計模式之單例模式中的餓漢與懶漢詳解
設(shè)計模式
設(shè)計模式是一套反復(fù)使用、多人知曉、經(jīng)過分類的代碼設(shè)計經(jīng)驗的總結(jié)。
如單例模式、工廠模式、觀察者模式等等。
單例模式
單例模式是指一個類只能創(chuàng)建一個對象,保證系統(tǒng)中該類只有一個實例,并提供一個可供訪問的全局訪問點,該實例被所有程序模塊共享,其中單例模式又分為了餓漢模式和懶漢模式兩種實現(xiàn)方式。
應(yīng)用
- 需要頻繁實例化然后銷毀的對象。
- 創(chuàng)建對象時耗時過多或者耗資源過多,但又經(jīng)常用到的對象。
- 有狀態(tài)的工具類對象。
- 頻繁訪問數(shù)據(jù)庫或文件的對象。
資源共享:
避免由于資源操作時導(dǎo)致的性能或損耗等。如日志文件,應(yīng)用配置等...
控制資源:
方便資源之間的互相通信。如線程池等...
實現(xiàn)
基本思想:
構(gòu)造函數(shù)為private私有屬性全局唯一公共訪問點來創(chuàng)建對象,函數(shù)為public屬性為了使用方便增加一個GC輔助類來釋放資源
餓漢模式
餓漢顧名思義就是很饑餓,迫不及待想吃東西,所以這種實現(xiàn)方式就是:無論以后對象會不會用到,程序一啟動時就創(chuàng)建一個唯一的實例對象
- 優(yōu)點:簡單,線程安全
- 缺點:可能導(dǎo)致進(jìn)程啟動慢、多個單例對象實例啟動順序不可控
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Singleton
{
public:
//提供靜態(tài)公有方法,可以類名加域名訪問,返回對象指針
static Singleton* GetInstance()
{
return m_instance;
}
class GCarbo
{
public:
~GCarbo()
{
cout << "delete instance" << endl;
if (nullptr != m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
private:
//構(gòu)造函數(shù)私有
Singleton(){
cout << "Singleton" << endl;
};
//防拷貝
//C++98
//Singleton(Singleton const&);
//Singleton& operator=(Singleton const&);
//C++11
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton *m_instance;
static GCarbo Carbo;
};
//在程序入口之前完成單例對象的初始化
Singleton* Singleton::m_instance = new Singleton;
Singleton::GCarbo Carbo;
int main()
{
cout << "main begin" << endl;
//均無法創(chuàng)建實例
//Singleton s;
//Singleton* p = new Singleton;
//調(diào)用共有靜態(tài)成員方法
Singleton* p1= Singleton::GetInstance();
Singleton* p2 = Singleton::GetInstance();
if (p1 == p2){
cout << "yes" << endl;
}
else{
cout << "no" << endl;
}
system("pause");
return 0;
}運行結(jié)果:
整個程序運行過程中只調(diào)用了一次構(gòu)造函數(shù),并且兩個對象為同一個對象,程序結(jié)束后自動銷毀對象

懶漢模式
相反,懶漢模式就是已經(jīng)懶到極致了,單例實例當(dāng)首次被引用時才將進(jìn)行初始化,盡量使資源的利用最大化。如最常見的晚綁定、寫時拷貝技術(shù)都是這種實現(xiàn)方式。
- 優(yōu)點:進(jìn)程啟動無負(fù)載,多個單例實例啟動順序可控制
- 缺點:復(fù)雜,線程不安全
但是這里要注意一點,不同于餓漢模式GetInstance全局公共訪問點僅僅返回一個類對象的指針,因為我們并沒有在類外實例化對象,所以我們要對對象的實例化進(jìn)行判斷,沒有對象時才能創(chuàng)建一個對象
static Singleton* GetInstance()
{
if (nullptr == m_instance){
m_instance = new Singleton();
}
return m_instance;
}完整代碼如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
if (nullptr == m_instance){
m_instance = new Singleton();
}
return m_instance;
}
//內(nèi)嵌垃圾回收類
class CGarbo{
public:
~CGarbo(){
cout << "delete instance" << endl;
if (nullptr != Singleton::m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
//定義一個靜態(tài)成員變量,程序結(jié)束后自動調(diào)用其析構(gòu)函數(shù)釋放單例對象
static CGarbo Garbo;
private:
//構(gòu)造函數(shù)私有
Singleton() {
printf("Singleton\n");
};
//防拷貝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
//單例對象指針
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
Singleton::CGarbo Garbo;
int main()
{
cout << "main begin" << endl;
cout << Singleton::GetInstance() << endl;
cout << Singleton::GetInstance() << endl;
system("pause");
return 0;
}運行結(jié)果:

這樣寫看似就可以了,兩個實例也創(chuàng)建了相同的對象,真的就可以了嘛?答案是否定的,現(xiàn)在我們在多線程環(huán)境下在對這段代碼進(jìn)行演示

我們發(fā)現(xiàn),兩個線程竟然創(chuàng)建了兩個不同的對象出來,這顯然不符合我們單例模式的要求,為什么會出現(xiàn)這種原因呢,假如第一個線程剛剛判斷完 m_instance 為 nullptr 開始創(chuàng)建對象,對象還未創(chuàng)建完成,第二個線程也開始判斷,此時對象未創(chuàng)建完成,條件滿足,也會繼續(xù)執(zhí)行對象創(chuàng)建的代碼創(chuàng)建對象,所以創(chuàng)建出了兩個不同的對象出來。
可能的線程不安全情況舉例(數(shù)字表示按時間片程序的執(zhí)行順序):
情況一(線程不安全):

情況二(編譯器自動優(yōu)化,進(jìn)行指令重排):

對于情況一解決辦法就是我們需要對創(chuàng)建對象的操作加互斥鎖,保證操作的原子性,由于加鎖后創(chuàng)建對象線程可能阻塞,所以這里我們?yōu)榱送瑫r保證效率和安全通常會選擇 Double-Check 方式加鎖
static Singleton* GetInstance()
{
//Double-Check方式加鎖保證效率和線程安全
//保證效率
if (nullptr == m_instance){
//保證線程安全
m_mtx.lock();
//正常檢查
if (nullptr == m_instance){
m_instance = new Singleton();
}
m_mtx.unlock();
}
return m_instance;
}但是情況二顯然程序還會出現(xiàn)問題,我們在 m_instance 對象前加上 volatile即可,禁止編譯器優(yōu)化,將變量從內(nèi)存中讀取,而不是從寄存器中讀取。
整個懶漢模式的完整代碼:
class Singleton
{
public:
static volatile Singleton* GetInstance()
{
//Double-Check方式加鎖保證效率和線程安全
//保證效率
if (nullptr == m_instance){
//保證線程安全
m_mtx.lock();
//正常檢查
if (nullptr == m_instance){
m_instance = new Singleton();
}
m_mtx.unlock();
}
return m_instance;
}
//內(nèi)嵌垃圾回收類
class CGarbo{
public:
~CGarbo(){
cout << "delete instance" << endl;
if (nullptr != Singleton::m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
//定義一個靜態(tài)成員變量,程序結(jié)束后自動調(diào)用其析構(gòu)函數(shù)釋放單例對象
static CGarbo Garbo;
private:
//構(gòu)造函數(shù)私有
Singleton() {
//cout原型operator <<() 連續(xù)的cout相當(dāng)于是函數(shù)的遞歸調(diào)用過程
//cout.operator<<(c1);
//由于線程的特性會導(dǎo)致輸出亂序
printf("Singleton\n");
};
//防拷貝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
//單例對象指針
static volatile Singleton* m_instance;
static mutex m_mtx;
};
volatile Singleton* Singleton::m_instance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
void func(int n)
{
printf("%d: %p\n", n, Singleton::GetInstance());
//cout << Singleton::GetInstance() << " " << n << endl;
}
int main()
{
cout << "main begin" << endl;
thread t1(func, 1);
thread t2(func, 2);
t1.join();
t2.join();
printf("%p\n", Singleton::GetInstance());
printf("%p\n", Singleton::GetInstance());
system("pause");
return 0;
}結(jié)果完全沒有問題:

到此這篇關(guān)于c語言設(shè)計模式之單例模式中的餓漢與懶漢詳解的文章就介紹到這了,更多相關(guān)c語言單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C++中String類模擬實現(xiàn)以及深拷貝淺拷貝
這篇文章主要介紹了詳解C++中String類模擬實現(xiàn)以及深拷貝淺拷貝的相關(guān)資料,希望通過本文能幫助到大家,讓大家實現(xiàn)這樣的方法,需要的朋友可以參考下2017-10-10
深入解讀C++ 內(nèi)聯(lián)函數(shù)inline|nullptr
內(nèi)聯(lián)函數(shù):用** inline 修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時C++編譯器會在調(diào)用的地方展開內(nèi)聯(lián)函數(shù)**,這樣調(diào)用內(nèi)聯(lián)函數(shù)就需要創(chuàng)建棧楨,就提高效率了,這篇文章給大家介紹C++ 內(nèi)聯(lián)函數(shù)inline|nullptr的相關(guān)知識,感興趣的朋友跟隨小編一起看看吧2024-07-07
C語言詳細(xì)分析宏定義與預(yù)處理命令的應(yīng)用
宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單的替換。字符串中可以含任何字符,可以是常數(shù),也可以是表達(dá)式,預(yù)處理程序?qū)λ蛔魅魏螜z查,如有錯誤,只能在編譯已被宏展開后的源程序時發(fā)現(xiàn)2022-07-07

