c語(yǔ)言設(shè)計(jì)模式之單例模式中的餓漢與懶漢詳解
設(shè)計(jì)模式
設(shè)計(jì)模式是一套反復(fù)使用、多人知曉、經(jīng)過(guò)分類的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。
如單例模式、工廠模式、觀察者模式等等。
單例模式
單例模式是指一個(gè)類只能創(chuàng)建一個(gè)對(duì)象,保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供一個(gè)可供訪問(wèn)的全局訪問(wèn)點(diǎn),該實(shí)例被所有程序模塊共享,其中單例模式又分為了餓漢模式和懶漢模式兩種實(shí)現(xiàn)方式。
應(yīng)用
- 需要頻繁實(shí)例化然后銷毀的對(duì)象。
- 創(chuàng)建對(duì)象時(shí)耗時(shí)過(guò)多或者耗資源過(guò)多,但又經(jīng)常用到的對(duì)象。
- 有狀態(tài)的工具類對(duì)象。
- 頻繁訪問(wèn)數(shù)據(jù)庫(kù)或文件的對(duì)象。
資源共享:
避免由于資源操作時(shí)導(dǎo)致的性能或損耗等。如日志文件,應(yīng)用配置等...
控制資源:
方便資源之間的互相通信。如線程池等...
實(shí)現(xiàn)
基本思想:
構(gòu)造函數(shù)為private私有屬性全局唯一公共訪問(wèn)點(diǎn)來(lái)創(chuàng)建對(duì)象,函數(shù)為public屬性為了使用方便增加一個(gè)GC輔助類來(lái)釋放資源
餓漢模式
餓漢顧名思義就是很饑餓,迫不及待想吃東西,所以這種實(shí)現(xiàn)方式就是:無(wú)論以后對(duì)象會(huì)不會(huì)用到,程序一啟動(dòng)時(shí)就創(chuàng)建一個(gè)唯一的實(shí)例對(duì)象
- 優(yōu)點(diǎn):簡(jiǎn)單,線程安全
- 缺點(diǎn):可能導(dǎo)致進(jìn)程啟動(dòng)慢、多個(gè)單例對(duì)象實(shí)例啟動(dòng)順序不可控
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Singleton { public: //提供靜態(tài)公有方法,可以類名加域名訪問(wèn),返回對(duì)象指針 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; }; //在程序入口之前完成單例對(duì)象的初始化 Singleton* Singleton::m_instance = new Singleton; Singleton::GCarbo Carbo; int main() { cout << "main begin" << endl; //均無(wú)法創(chuàng)建實(shí)例 //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; }
運(yùn)行結(jié)果:
整個(gè)程序運(yùn)行過(guò)程中只調(diào)用了一次構(gòu)造函數(shù),并且兩個(gè)對(duì)象為同一個(gè)對(duì)象,程序結(jié)束后自動(dòng)銷毀對(duì)象
懶漢模式
相反,懶漢模式就是已經(jīng)懶到極致了,單例實(shí)例當(dāng)首次被引用時(shí)才將進(jìn)行初始化,盡量使資源的利用最大化。如最常見的晚綁定、寫時(shí)拷貝技術(shù)都是這種實(shí)現(xiàn)方式。
- 優(yōu)點(diǎn):進(jìn)程啟動(dòng)無(wú)負(fù)載,多個(gè)單例實(shí)例啟動(dòng)順序可控制
- 缺點(diǎn):復(fù)雜,線程不安全
但是這里要注意一點(diǎn),不同于餓漢模式GetInstance全局公共訪問(wèn)點(diǎn)僅僅返回一個(gè)類對(duì)象的指針,因?yàn)槲覀儾]有在類外實(shí)例化對(duì)象,所以我們要對(duì)對(duì)象的實(shí)例化進(jìn)行判斷,沒有對(duì)象時(shí)才能創(chuàng)建一個(gè)對(duì)象
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; } } }; //定義一個(gè)靜態(tài)成員變量,程序結(jié)束后自動(dòng)調(diào)用其析構(gòu)函數(shù)釋放單例對(duì)象 static CGarbo Garbo; private: //構(gòu)造函數(shù)私有 Singleton() { printf("Singleton\n"); }; //防拷貝 Singleton(Singleton const&); Singleton& operator=(Singleton const&); //單例對(duì)象指針 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; }
運(yùn)行結(jié)果:
這樣寫看似就可以了,兩個(gè)實(shí)例也創(chuàng)建了相同的對(duì)象,真的就可以了嘛?答案是否定的,現(xiàn)在我們?cè)诙嗑€程環(huán)境下在對(duì)這段代碼進(jìn)行演示
我們發(fā)現(xiàn),兩個(gè)線程竟然創(chuàng)建了兩個(gè)不同的對(duì)象出來(lái),這顯然不符合我們單例模式的要求,為什么會(huì)出現(xiàn)這種原因呢,假如第一個(gè)線程剛剛判斷完 m_instance 為 nullptr 開始創(chuàng)建對(duì)象,對(duì)象還未創(chuàng)建完成,第二個(gè)線程也開始判斷,此時(shí)對(duì)象未創(chuàng)建完成,條件滿足,也會(huì)繼續(xù)執(zhí)行對(duì)象創(chuàng)建的代碼創(chuàng)建對(duì)象,所以創(chuàng)建出了兩個(gè)不同的對(duì)象出來(lái)。
可能的線程不安全情況舉例(數(shù)字表示按時(shí)間片程序的執(zhí)行順序):
情況一(線程不安全):
情況二(編譯器自動(dòng)優(yōu)化,進(jìn)行指令重排):
對(duì)于情況一解決辦法就是我們需要對(duì)創(chuàng)建對(duì)象的操作加互斥鎖,保證操作的原子性,由于加鎖后創(chuàng)建對(duì)象線程可能阻塞,所以這里我們?yōu)榱送瑫r(shí)保證效率和安全通常會(huì)選擇 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; }
但是情況二顯然程序還會(huì)出現(xiàn)問(wèn)題,我們?cè)?m_instance 對(duì)象前加上 volatile即可,禁止編譯器優(yōu)化,將變量從內(nèi)存中讀取,而不是從寄存器中讀取。
整個(gè)懶漢模式的完整代碼:
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; } } }; //定義一個(gè)靜態(tài)成員變量,程序結(jié)束后自動(dòng)調(diào)用其析構(gòu)函數(shù)釋放單例對(duì)象 static CGarbo Garbo; private: //構(gòu)造函數(shù)私有 Singleton() { //cout原型operator <<() 連續(xù)的cout相當(dāng)于是函數(shù)的遞歸調(diào)用過(guò)程 //cout.operator<<(c1); //由于線程的特性會(huì)導(dǎo)致輸出亂序 printf("Singleton\n"); }; //防拷貝 Singleton(Singleton const&); Singleton& operator=(Singleton const&); //單例對(duì)象指針 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é)果完全沒有問(wèn)題:
到此這篇關(guān)于c語(yǔ)言設(shè)計(jì)模式之單例模式中的餓漢與懶漢詳解的文章就介紹到這了,更多相關(guān)c語(yǔ)言單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語(yǔ)言常量定義規(guī)則知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是關(guān)于c語(yǔ)言常量定義規(guī)則知識(shí)點(diǎn)總結(jié),需要的朋友們可以學(xué)習(xí)下。2020-03-03從頭學(xué)習(xí)C語(yǔ)言之二維數(shù)組
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言之二維數(shù)組,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01詳解C++中String類模擬實(shí)現(xiàn)以及深拷貝淺拷貝
這篇文章主要介紹了詳解C++中String類模擬實(shí)現(xiàn)以及深拷貝淺拷貝的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的方法,需要的朋友可以參考下2017-10-10關(guān)于C++11中限定作用域的枚舉類型的問(wèn)題
C++中有兩種類型的枚舉:不限定作用域的枚舉類型和限定作用域的枚舉類型。限定作用域的枚舉類型是C++11標(biāo)準(zhǔn)引入的新類型,對(duì)C++11中限定作用域的枚舉類型相關(guān)知識(shí)感興趣的朋友一起看看吧2022-01-01深入解讀C++ 內(nèi)聯(lián)函數(shù)inline|nullptr
內(nèi)聯(lián)函數(shù):用** inline 修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時(shí)C++編譯器會(huì)在調(diào)用的地方展開內(nèi)聯(lián)函數(shù)**,這樣調(diào)用內(nèi)聯(lián)函數(shù)就需要?jiǎng)?chuàng)建棧楨,就提高效率了,這篇文章給大家介紹C++ 內(nèi)聯(lián)函數(shù)inline|nullptr的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2024-07-07C語(yǔ)言詳細(xì)分析宏定義與預(yù)處理命令的應(yīng)用
宏定義是用宏名來(lái)表示一個(gè)字符串,在宏展開時(shí)又以該字符串取代宏名,這只是一種簡(jiǎn)單的替換。字符串中可以含任何字符,可以是常數(shù),也可以是表達(dá)式,預(yù)處理程序?qū)λ蛔魅魏螜z查,如有錯(cuò)誤,只能在編譯已被宏展開后的源程序時(shí)發(fā)現(xiàn)2022-07-07C語(yǔ)言實(shí)現(xiàn)雙人貪吃蛇游戲?qū)嵗a
大家好,本篇文章主要講的是C語(yǔ)言實(shí)現(xiàn)雙人貪吃蛇游戲?qū)嵗a,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12