單例模式的實(shí)現(xiàn)分析

  • 單例模式(Singleton),也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對象的類必須保證只有一個(gè)實(shí)例存在。
    許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。
    比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的
    其他對象再通過這個(gè)單例對象獲取這些配置信息。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。
    ——但是如果沒有學(xué)過設(shè)計(jì)模式的人,可能不會(huì)想到要去應(yīng)用單例模式,面對單例模式適用的情況,可能會(huì)優(yōu)先考慮使用全局或者靜態(tài)變量的方式,
    這樣比較簡單,也是沒學(xué)過設(shè)計(jì)模式的人所能想到的最簡單的方式了。如果采用全局或者靜態(tài)變量的方式,會(huì)影響封裝性,難以保證別的代碼不會(huì)對全局變量造成影響。
    而使用單例模式,將類設(shè)計(jì)成單例,成員變量都設(shè)成私有,要修改的話只能通過調(diào)用成員函數(shù),更安全。

  • 適用場景:
    單例模式只允許創(chuàng)建一個(gè)對象,因此節(jié)省內(nèi)存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個(gè)模塊使用同一個(gè)數(shù)據(jù)源連接對象等等。如:
    1.需要頻繁實(shí)例化然后銷毀的對象。
    2.創(chuàng)建對象時(shí)耗時(shí)過多或者耗資源過多,但又經(jīng)常用到的對象。
    3.有狀態(tài)的工具類對象。
    4.頻繁訪問數(shù)據(jù)庫或文件的對象。
    以下都是單例模式的經(jīng)典使用場景:
    1.資源共享的情況下,避免由于資源操作時(shí)導(dǎo)致的性能或損耗等。如上述中的日志文件,應(yīng)用配置。
    2.控制資源的情況下,方便資源之間的互相通信。如線程池等。

  • 應(yīng)用場景舉例:

  1. 外部資源:每臺計(jì)算機(jī)有若干個(gè)打印機(jī),但只能有一個(gè)PrinterSpooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)。
    內(nèi)部資源:大多數(shù)軟件都有一個(gè)(或多個(gè))屬性文件存放系統(tǒng)配置,這樣的系統(tǒng)應(yīng)該有一個(gè)對象管理這些屬性文件
  2. Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式(這個(gè)很熟悉吧),想想看,是不是呢,你能打開兩個(gè)windows task manager嗎? 不信你自己試試看哦~
  3. windows的Recycle Bin(回收站)也是典型的單例應(yīng)用。在整個(gè)系統(tǒng)運(yùn)行過程中,回收站一直維護(hù)著僅有的一個(gè)實(shí)例。
  4. 網(wǎng)站的計(jì)數(shù)器,一般也是采用單例模式實(shí)現(xiàn),否則難以同步。
  5. 應(yīng)用程序的日志應(yīng)用,一般都何用單例模式實(shí)現(xiàn),這一般是由于共享的日志文件一直處于打開狀態(tài),因?yàn)橹荒苡幸粋€(gè)實(shí)例去操作,否則內(nèi)容不好追加。
  6. Web應(yīng)用的配置對象的讀取,一般也應(yīng)用單例模式,這個(gè)是由于配置文件是共享的資源。
  7. 數(shù)據(jù)庫連接池的設(shè)計(jì)一般也是采用單例模式,因?yàn)閿?shù)據(jù)庫連接是一種數(shù)據(jù)庫資源。數(shù)據(jù)庫軟件系統(tǒng)中使用數(shù)據(jù)庫連接池,
    主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因?yàn)楹斡脝卫J絹砭S護(hù),就可以大大降低這種損耗。
  8. 多線程的線程池的設(shè)計(jì)一般也是采用單例模式,這是由于線程池要方便對池中的線程進(jìn)行控制。
  9. 操作系統(tǒng)的文件系統(tǒng),也是大的單例模式實(shí)現(xiàn)的具體例子,一個(gè)操作系統(tǒng)只能有一個(gè)文件系統(tǒng)。
  10. 單例模式常常與工廠模式結(jié)合使用,因?yàn)楣S只需要?jiǎng)?chuàng)建產(chǎn)品實(shí)例就可以了,在多線程的環(huán)境下也不會(huì)造成任何的沖突,因此只需要一個(gè)工廠實(shí)例就可以了。
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<mutex>
using namespace std;

/*
———————————— C語言下的單例模式 —————————————————
最簡單的就是定義一個(gè)全局變量,這個(gè)全局變量在全局唯一代表一種東西
或者將所有的全局變量集中放在一個(gè)結(jié)構(gòu)體中并且全局只有一個(gè)結(jié)構(gòu)體對象globals
*/
struct C_singleton_globals
{
    int loop_flag;  //可以是程序中控制某個(gè)while死循環(huán)的終止條件
    char config_log_path; //可以是統(tǒng)一存放日志信息的位置
};
//全局唯一的結(jié)構(gòu)體對象指針
typedef struct C_singleton_globals*  C_singleton_globals_t;
C_singleton_globals_t globals_t = NULL;
//若要放到堆內(nèi)存中,可以寫一個(gè)初始化函數(shù)分配內(nèi)存
C_singleton_globals_t init_gobals_t()
{
    //如果還沒有被初始化過,那就分配內(nèi)存
    if (NULL == globals_t)
    {
        globals_t = (struct C_singleton_globals *)malloc(sizeof(struct C_singleton_globals));
        printf("為全局變量分配內(nèi)存成功 !\n");
    }
    else
    {
        printf("為全局變量分配內(nèi)存失敗,之前已分配過 !\n");
    }
    return globals_t;
}

/*
——————————————————C++實(shí)現(xiàn)單例模式—————————————
1、將構(gòu)造函數(shù)設(shè)計(jì)成private,使得不能隨便產(chǎn)生新的對象
2、將唯一的實(shí)例對象設(shè)計(jì)成private下的static成員變量,static的作用是使得
//成員變成類擁有的,而不是某個(gè)實(shí)例對象擁有的
3、設(shè)計(jì)一個(gè)public的getInstance()方法,先檢查是否實(shí)例過,
//若沒有再分配內(nèi)存實(shí)例出來一個(gè)對象
*/

//1、只適合在單線程環(huán)境下使用的代碼,若是多線程,
//也可能剛好出現(xiàn)同時(shí)調(diào)用getInstance()函數(shù),而導(dǎo)致產(chǎn)生多個(gè)實(shí)例
class singleton1
{
private:
    //私有構(gòu)造函數(shù),只能從類的成員函數(shù)來訪問,不能從外部訪問
    singleton1(){   }
    //為了避免值復(fù)制時(shí)產(chǎn)生新的對象副本,除了將構(gòu)造函數(shù)置為私有外,復(fù)制構(gòu)造函數(shù)也要特別聲明并置為私有。
    singleton1(const singleton1 &);
    //私有靜態(tài)成員變量的聲明
    static singleton1 *pInstance1;

public:
    static singleton1 * getInstance()
    {
        if (NULL == pInstance1)
        {
            pInstance1 = new singleton1();
            cout << "成功實(shí)例化pSingle1" << endl;
        }
        else
        {
            cout << "實(shí)例化pSingle1失敗,已存在" << endl;
        }
        return pInstance1;
    }
};
//類的靜態(tài)成員的定義(所以還需要指定類型)和賦值(類中只是聲明),放在類的外部
//這句代碼如果放到main函數(shù)中肯定會(huì)報(bào)錯(cuò),要放在main外部
singleton1 * singleton1::pInstance1 = NULL;

//2、適合在多線程環(huán)境下使用的代碼,增加線程互斥鎖
class singleton2
{
private:
    //私有構(gòu)造函數(shù),只能從類的成員函數(shù)來訪問,不能從外部訪問
    singleton2(){   }
    //為了避免值復(fù)制時(shí)產(chǎn)生新的對象副本,除了將構(gòu)造函數(shù)置為私有外,
//復(fù)制構(gòu)造函數(shù)也要特別聲明并置為私有。
    singleton2(const singleton2 &);
    //私有靜態(tài)成員變量的聲明
    static singleton2 *pInstance2;
    //獨(dú)占式互斥量,一段時(shí)間內(nèi)僅一個(gè)線程可以訪問
    static mutex mtx;   //設(shè)為static是為了給下面的static函數(shù)訪問,則要在類定義后面初始化

public:
    static singleton2 * getInstance()
    {
        //增加這個(gè)判斷(著名的DCL技法,即Double Check Lock雙重鎖定),
//可以不需要每次獲取同一實(shí)例都先加鎖再解鎖,浪費(fèi)資源
        if (NULL == pInstance2) 
        {
            mtx.lock(); //互斥加鎖
            if (NULL == pInstance2)
            {
                pInstance2 = new singleton2();
                cout << "成功實(shí)例化pSingle2" << endl;
            }
            mtx.unlock();   //互斥解鎖
        }
        else
        {
            cout << "實(shí)例化pSingle2失敗,已存在" << endl;
        }
        return pInstance2;
    }
};
//類的靜態(tài)成員的定義(所以還需要指定類型)和賦值(類中只是聲明),放在類的外部
//這句代碼如果放到main函數(shù)中肯定會(huì)報(bào)錯(cuò),要放在main外部
singleton2 * singleton2::pInstance2 = NULL;
mutex singleton2::mtx;  //全局變量

//3、懶漢模式最佳實(shí)現(xiàn)代碼,適用于多線程,無需加鎖
class singleton3
{
private:
    //私有構(gòu)造函數(shù),只能從類的成員函數(shù)來訪問,不能從外部訪問
    singleton3(){   }
    //為了避免值復(fù)制時(shí)產(chǎn)生新的對象副本,除了將構(gòu)造函數(shù)置為私有外,
//復(fù)制構(gòu)造函數(shù)也要特別聲明并置為私有。
    singleton3(const singleton3 &);

public:
    static singleton3 * getInstance()
    {
        //C++11規(guī)定,在一個(gè)線程開始local static 對象的初始化后完成初始化前,
//其他線程執(zhí)行到這個(gè)local static對象的初始化語句就會(huì)等待,
        //直到該local static 對象初始化完成。所以C++11標(biāo)準(zhǔn)下local static對象初始化在多線程條件下安全
        static singleton3 instance3;
        return &instance3;
    }
};
//以上都是懶漢單例模式,能拖多久就拖多久,即只有在第一次調(diào)用getInstance函數(shù)后才有實(shí)例產(chǎn)生。用時(shí)間換取空間—
//——————下面是餓漢單例模式,即程序一運(yùn)行就立即產(chǎn)生實(shí)例,不管后面啥時(shí)候要用,甚至不用。用空間換取時(shí)間————
class singleton4
{
private:
    //私有構(gòu)造函數(shù)
    singleton4(){ }
    //為了避免值復(fù)制時(shí)產(chǎn)生新的對象副本,除了將構(gòu)造函數(shù)置為私有外,復(fù)制構(gòu)造函數(shù)也要特別聲明并置為私有。
    singleton4(const singleton4 &);
    //私有靜態(tài)指針聲明
    static singleton4 *pInstance4;

public:
    static singleton4 * getInstance()
    {
        return pInstance4;
    }
};
//直接在給靜態(tài)成員定義時(shí),創(chuàng)建實(shí)例
//為何這里又可以直接調(diào)用私有構(gòu)造函數(shù)呢?因?yàn)檫@里是全局環(huán)境,不屬于任何函數(shù)內(nèi)部調(diào)用(如main函數(shù))
singleton4 * singleton4::pInstance4 = new singleton4();

//測試代碼 
int main()
{
    C_singleton_globals_t p_g1 = init_gobals_t();
    C_singleton_globals_t p_g2 = init_gobals_t();
    singleton1 *ps1_1 = singleton1::getInstance();
    singleton1 *ps1_2 = singleton1::getInstance();
    //singleton1 s1_3;  //這句相當(dāng)于調(diào)用singleton1()構(gòu)造函數(shù),只是開辟的棧內(nèi)存,也不能外部調(diào)用構(gòu)造函數(shù)
    //singleton1 *ps1_3 = new singleton1(); //構(gòu)造函數(shù)私有,不能在外部調(diào)用
    //singleton1 ps1_3(*ps1_2);     //拷貝賦值操作創(chuàng)建實(shí)例副本,該構(gòu)造函數(shù)也設(shè)為私有,也不能外部調(diào)用
    //singleton1 ps1_4 = *ps1_2;        //同上

    singleton2 *ps2_1 = singleton2::getInstance();
    singleton2 *ps2_2 = singleton2::getInstance();

    singleton3 *ps3_1 = singleton3::getInstance();
    singleton3 *ps3_2 = singleton3::getInstance();
    cout << "最經(jīng)典懶漢模式返回函數(shù)局部靜態(tài)實(shí)例指針,實(shí)例地址:" << ps3_1 << endl;
    cout << "最經(jīng)典懶漢模式返回函數(shù)局部靜態(tài)實(shí)例指針,實(shí)例地址:" << ps3_2 << endl;
    
    singleton4 *ps4_1 = singleton4::getInstance();
    singleton4 *ps4_2 = singleton4::getInstance();
    cout << "最經(jīng)典惡漢模式返回類全局靜態(tài)實(shí)例指針,實(shí)例地址:" << ps4_1 << endl;
    cout << "最經(jīng)典惡漢模式返回類全局靜態(tài)實(shí)例指針,實(shí)例地址:" << ps4_2 << endl;
}
  • 如果要設(shè)定只能創(chuàng)建5個(gè)實(shí)例,怎么做?比如限制某個(gè)窗口應(yīng)用程序只能最多開5個(gè)窗口
    //0、只適合于懶漢模式,在需要的時(shí)候再創(chuàng)建實(shí)例
    //1、增加一個(gè)類的私有靜態(tài)成員變量static int instanceCount和一個(gè)靜態(tài)對象指針數(shù)組
    //2、在getInstance()函數(shù)中判斷instanceCount>=5,如果沒有的話則new創(chuàng)建實(shí)例,并且instanceCount++,然后返回實(shí)例指針
    //3、如果判斷的實(shí)例已經(jīng)超過了5個(gè),則返回空指針或者返回靜態(tài)對象指針數(shù)組中的一個(gè)對象指針

  • 如果要增加析構(gòu)函數(shù),釋放類實(shí)例并關(guān)閉打開的其他資源文件怎么辦?
    //1、增加私有的析構(gòu)函數(shù),并在析構(gòu)函數(shù)里寫上關(guān)閉資源文件的語句
    //2、增加public的deleteInstance()函數(shù),里面只寫兩句:delete pInstence; pInstance = NULL; 即將唯一new的實(shí)例釋放,
    //則會(huì)先自動(dòng)調(diào)用析構(gòu)函數(shù)關(guān)閉其他資源,然后才釋放開辟的內(nèi)存

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 單例模式 什么是單例模式?我就不多做贅述了。移步至百度百科單例模式。 什么時(shí)候使用單例? 單例模式是一個(gè)經(jīng)典的設(shè)計(jì)...
    小癡_閱讀 6,793評論 8 53
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,711評論 19 139
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮啙嵰锥?,是?xiàng)目中最...
    成熱了閱讀 4,549評論 4 34
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,286評論 8 265
  • “我覺得你變了” “人是會(huì)變的,我們需要往前走” 一 昨天有個(gè)姑娘在微信后臺和我說,她昨天去見了她的一個(gè)初中同學(xué),...
    劉Toyn閱讀 388評論 0 2

友情鏈接更多精彩內(nèi)容