设计模式——单例模式(懒汉模式,饿汉模式)
聲明: 本博客參考C語言中文網(wǎng)和優(yōu)秀博客總結(jié)得出:
(1)C語言中文網(wǎng)鏈接
(2)優(yōu)秀博客鏈接
單例模式的定義: 指一個類只有一個實例,且該類能自行創(chuàng)建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內(nèi)存資源的浪費,或出現(xiàn)各個窗口顯示內(nèi)容的不一致等錯誤。
在計算機系統(tǒng)中,還有 Windows 的回收站、操作系統(tǒng)中的文件系統(tǒng)、多線程中的線程池、打印機的后臺處理服務、應用程序中的對話框、系統(tǒng)中的緩存等常常被設計成單例。
單例模式在現(xiàn)實生活中的應用也非常廣泛,例如公司 CEO、部門經(jīng)理等都屬于單例模型。
單例模式特點:
(1)單例類只有一個實例對象;
(2)該單例對象必須由單例類自行創(chuàng)建;
(3)單例類對外提供一個訪問該單例的全局訪問點。
為了滿足以上三個特點,我們通常對類進行以下設計:
(1)將構(gòu)造函數(shù)設置為私有。(這樣外界就不可以隨便實例化對象了)
(2)有一個指向?qū)嵗撵o態(tài)指針。
(3)用一個靜態(tài)成員方法(或者全局友元函數(shù),只不過我們通常都是用靜態(tài)成員方法)來實例化對象,并且對實例化進行控制,只實例化一次,讓靜態(tài)指針指向?qū)嵗?#xff0c;第一次進行實例操作,后面都直接返回已經(jīng)指向?qū)嵗撵o態(tài)指針變量本身。
單例模式的優(yōu)點和缺點:
單例模式的優(yōu)點:
(1)單例模式可以保證內(nèi)存里只有一個實例,減少了內(nèi)存的開銷。
(2)可以避免對資源的多重占用。
(3)單例模式設置全局訪問點,可以優(yōu)化和共享資源的訪問。
單例模式的缺點:
(1)單例模式一般沒有接口,擴展困難。如果要擴展,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。
(2)在并發(fā)測試中,單例模式不利于代碼調(diào)試。在調(diào)試過程中,如果單例中的代碼沒有執(zhí)行完,也不能模擬生成一個新的對象。
(3)單例模式的功能代碼通常寫在一個類中,如果功能設計不合理,則很容易違背單一職責原則。
一、懶漢模式
根據(jù)代碼看問題:
//單例模式-懶漢模式 class singleton_idler { public:static singleton_idler* Get_objectptr()//靜態(tài)成員方法{if (object_ptr == nullptr){mtx.lock();if (object_ptr == nullptr){object_ptr = new singleton_idler;}mtx.unlock();}return object_ptr;}int& Get_val(){return m_val;}private:singleton_idler() {}static singleton_idler* object_ptr;static int m_val;static mutex mtx; };mutex singleton_idler::mtx; int singleton_idler::m_val = 10; singleton_idler* singleton_idler::object_ptr = nullptr;int main() {singleton_idler* ptr1 = singleton_idler::Get_objectptr();cout << "ptr1指向的實例地址為:" << ptr1 << "\t" << "m_val地址為:"<< &(ptr1->Get_val()) << "\t" << "m_val值為:" << ptr1->Get_val() << endl;singleton_idler* ptr2 = singleton_idler::Get_objectptr();cout << "ptr1指向的實例地址為:" << ptr2 << "\t" << "m_val地址為:"<< &(ptr2->Get_val()) << "\t" << "m_val值為:" << ptr2->Get_val() << endl;return 0; }運行結(jié)果:
可以通過兩個指針指向的實例的地址看出來,兩個指針指向的是同一個實例,整個過程就只實例化出來一個對象。
這就是懶漢模式,你需要的時候才給你創(chuàng)建一個實例,而且后面你用的就只有這一個實例,創(chuàng)建一個實例后,就不創(chuàng)建了。
構(gòu)造函數(shù)被設置成私有,就是為了讓創(chuàng)建對像的權(quán)力只交給下面這個靜態(tài)函數(shù):
這個靜態(tài)方法是整個懶漢模式的核心,而這個靜態(tài)方法的核心就是創(chuàng)建對象,對象的創(chuàng)建被層層保護起來,就是為了確保只實例化一次。
如果object_ptr不為nullptr,說明已經(jīng)創(chuàng)建過對象了(注意,object_ptr是靜態(tài)指針變量),那么就直接返回指針本身就好了。如果object_ptr沒有創(chuàng)建過對象,那么object_ptr就是nullptr(注意代碼中,靜態(tài)指針成員變量object_ptr,我們在類外初始化為nullptr了),那么就會進到第一個if語句,而進到第一個if語句加鎖是因為,在多線程的情況下,在沒有實例化對象的情況下,可能兩個地方同時調(diào)用這個靜態(tài)方法,那么此時兩個地方用的是同一個object_ptr都是nullptr,那么都會進到第一層for循環(huán),所以第一層for循環(huán)里面加鎖了,此時只能一個線程拿到鎖,進到里面。那么為什么鎖里面又有個if語句呢? 注意,此時一個線程拿到鎖了,進去了,還一個線程沒有拿到鎖,就阻塞在mtx.lock()這個地方了,當進去的線程實例化完成后,釋放鎖,在mtx.lock()被阻塞的線程就會拿到鎖,如果此時沒有里面那一層if語句,這個線程就會重新new一個對象,將object_ptr重新指向這個對象,這只是兩個線程可能出現(xiàn)的問題,如果更多個線程進到第一個if語句,就會new很多次對象了。當然,如果已經(jīng)object_ptr已經(jīng)不為nullptr了,那么沒有進到第一個if語句的線程,第一個if語句就進不去了。所以最安全的辦法就是鎖外鎖內(nèi)都有一個if語句。
當然,在博客上還可以看到一種寫法:
將鎖外面的if語句去掉了,這樣也是線程安全的,因為鎖只能被一個線程拿到,當?shù)谝粋€實例化對象之后,其他線程拿到鎖進去之后,if語句判斷就會發(fā)現(xiàn)object_ptr不為nullptr,就不會再去實例化了。
但是這段代碼和上面兩個if語句的代碼有點區(qū)別,就是當已經(jīng)實例化過對象之后,多個線程同時進到這個靜態(tài)方法里面之后,這個時候就會只有一個線程拿到鎖,去判斷if語句,而其他線程就會都阻塞到mtx.lock這里,然后前面的線程拿到鎖判斷完if語句之后,解鎖,被阻塞的這些線程會搶鎖,有一個線程搶到,進入到里面,進行if語句判斷,其他線程阻塞到mtx.lock()這里…
而兩個if語句的代碼,當已經(jīng)實例化過后,就不存在阻塞到mtx.lock()這里的情況了,因為第一個if語句判斷完不為nullptr之后就直接return object_ptr了。
二、餓漢模式
餓漢模式和單例模式的區(qū)別在于,懶漢模式在你需要的時候才去創(chuàng)建,餓漢模式利用靜態(tài)指針變量存在數(shù)據(jù)區(qū)的特點,在一開始就直接創(chuàng)建一個實例(在線程創(chuàng)建之前),所以餓漢模式的線程肯定是安全的。
代碼示例:
運行結(jié)果:
由于實例在一開始創(chuàng)建,而且在這之后不再可能創(chuàng)建,除非重新編譯運行,為了不讓實例自己析構(gòu),我們就將析構(gòu)函數(shù)寫成私有。
將析構(gòu)函數(shù)寫成私有之后,那么實例就只能等程序運行結(jié)束,自動回收實例占有的堆空間了。然后如果想要在程序運行結(jié)束之前析構(gòu)實例,就成了難題,(其實我在想,我們將析構(gòu)函數(shù)寫成私有就是為了讓實例在整個程序運行的時候都存在,然后將析構(gòu)函數(shù)寫成私有之后,我們卻又在想怎么析構(gòu)實例,那么我們?yōu)榱俗寣嵗谡麄€程序運行階段都存在而把析構(gòu)函數(shù)寫成私有的意義呢?🤐,但是這些解決辦法都了解了解挺好的),有以下幾點解決辦法:
(1)在類中寫一個釋放資源的方法
運行結(jié)果:
(2)定義一個內(nèi)部類
內(nèi)部類的對象生存期到了之后,會調(diào)用內(nèi)部類的析構(gòu)函數(shù),我們在內(nèi)部類的析構(gòu)函數(shù)中對singleton_hungry的實例進行析構(gòu)。
運行結(jié)果:
(3)利用智能指針
對于這個方法,我實踐了,但是沒有證據(jù)證明調(diào)用了實例的析構(gòu)函數(shù)進行對象析構(gòu)了(哪怕在析構(gòu)函數(shù)里做上標記)。
總的來說,對于餓漢模式,我們將析構(gòu)函數(shù)設置成私有,是為了程序運行的整個過程,實例都存在,程序結(jié)束,系統(tǒng)會自動回收堆空間,如果說不想要等程序結(jié)束自動回收,那就別把析構(gòu)函數(shù)設置成私有了,省的還大費周章的考慮用其他的方式來析構(gòu)。
一般我們都會將析構(gòu)函數(shù)設置成私有,等程序結(jié)束,讓系統(tǒng)自動回收。
博客參考鏈接:優(yōu)秀博客鏈接
總結(jié)
以上是生活随笔為你收集整理的设计模式——单例模式(懒汉模式,饿汉模式)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单链表——判断两个单链表(无头节点)是否
- 下一篇: asp.net ajax控件工具集 Au