单例模式的实现分析

  • 单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
    许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
    比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的
    其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
    ——但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态变量的方式,
    这样比较简单,也是没学过设计模式的人所能想到的最简单的方式了。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。
    而使用单例模式,将类设计成单例,成员变量都设成私有,要修改的话只能通过调用成员函数,更安全。

  • 适用场景:
    单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
    1.需要频繁实例化然后销毁的对象。
    2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
    3.有状态的工具类对象。
    4.频繁访问数据库或文件的对象。
    以下都是单例模式的经典使用场景:
    1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
    2.控制资源的情况下,方便资源之间的互相通信。如线程池等。

  • 应用场景举例:

  1. 外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
    内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
  2. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
  3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,
    主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  10. 单例模式常常与工厂模式结合使用,因为工厂只需要创建产品实例就可以了,在多线程的环境下也不会造成任何的冲突,因此只需要一个工厂实例就可以了。
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<mutex>
using namespace std;

/*
———————————— C语言下的单例模式 —————————————————
最简单的就是定义一个全局变量,这个全局变量在全局唯一代表一种东西
或者将所有的全局变量集中放在一个结构体中并且全局只有一个结构体对象globals
*/
struct C_singleton_globals
{
    int loop_flag;  //可以是程序中控制某个while死循环的终止条件
    char config_log_path; //可以是统一存放日志信息的位置
};
//全局唯一的结构体对象指针
typedef struct C_singleton_globals*  C_singleton_globals_t;
C_singleton_globals_t globals_t = NULL;
//若要放到堆内存中,可以写一个初始化函数分配内存
C_singleton_globals_t init_gobals_t()
{
    //如果还没有被初始化过,那就分配内存
    if (NULL == globals_t)
    {
        globals_t = (struct C_singleton_globals *)malloc(sizeof(struct C_singleton_globals));
        printf("为全局变量分配内存成功 !\n");
    }
    else
    {
        printf("为全局变量分配内存失败,之前已分配过 !\n");
    }
    return globals_t;
}

/*
——————————————————C++实现单例模式—————————————
1、将构造函数设计成private,使得不能随便产生新的对象
2、将唯一的实例对象设计成private下的static成员变量,static的作用是使得
//成员变成类拥有的,而不是某个实例对象拥有的
3、设计一个public的getInstance()方法,先检查是否实例过,
//若没有再分配内存实例出来一个对象
*/

//1、只适合在单线程环境下使用的代码,若是多线程,
//也可能刚好出现同时调用getInstance()函数,而导致产生多个实例
class singleton1
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton1(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。
    singleton1(const singleton1 &);
    //私有静态成员变量的声明
    static singleton1 *pInstance1;

public:
    static singleton1 * getInstance()
    {
        if (NULL == pInstance1)
        {
            pInstance1 = new singleton1();
            cout << "成功实例化pSingle1" << endl;
        }
        else
        {
            cout << "实例化pSingle1失败,已存在" << endl;
        }
        return pInstance1;
    }
};
//类的静态成员的定义(所以还需要指定类型)和赋值(类中只是声明),放在类的外部
//这句代码如果放到main函数中肯定会报错,要放在main外部
singleton1 * singleton1::pInstance1 = NULL;

//2、适合在多线程环境下使用的代码,增加线程互斥锁
class singleton2
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton2(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,
//复制构造函数也要特别声明并置为私有。
    singleton2(const singleton2 &);
    //私有静态成员变量的声明
    static singleton2 *pInstance2;
    //独占式互斥量,一段时间内仅一个线程可以访问
    static mutex mtx;   //设为static是为了给下面的static函数访问,则要在类定义后面初始化

public:
    static singleton2 * getInstance()
    {
        //增加这个判断(著名的DCL技法,即Double Check Lock双重锁定),
//可以不需要每次获取同一实例都先加锁再解锁,浪费资源
        if (NULL == pInstance2) 
        {
            mtx.lock(); //互斥加锁
            if (NULL == pInstance2)
            {
                pInstance2 = new singleton2();
                cout << "成功实例化pSingle2" << endl;
            }
            mtx.unlock();   //互斥解锁
        }
        else
        {
            cout << "实例化pSingle2失败,已存在" << endl;
        }
        return pInstance2;
    }
};
//类的静态成员的定义(所以还需要指定类型)和赋值(类中只是声明),放在类的外部
//这句代码如果放到main函数中肯定会报错,要放在main外部
singleton2 * singleton2::pInstance2 = NULL;
mutex singleton2::mtx;  //全局变量

//3、懒汉模式最佳实现代码,适用于多线程,无需加锁
class singleton3
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton3(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,
//复制构造函数也要特别声明并置为私有。
    singleton3(const singleton3 &);

public:
    static singleton3 * getInstance()
    {
        //C++11规定,在一个线程开始local static 对象的初始化后完成初始化前,
//其他线程执行到这个local static对象的初始化语句就会等待,
        //直到该local static 对象初始化完成。所以C++11标准下local static对象初始化在多线程条件下安全
        static singleton3 instance3;
        return &instance3;
    }
};
//以上都是懒汉单例模式,能拖多久就拖多久,即只有在第一次调用getInstance函数后才有实例产生。用时间换取空间—
//——————下面是饿汉单例模式,即程序一运行就立即产生实例,不管后面啥时候要用,甚至不用。用空间换取时间————
class singleton4
{
private:
    //私有构造函数
    singleton4(){ }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。
    singleton4(const singleton4 &);
    //私有静态指针声明
    static singleton4 *pInstance4;

public:
    static singleton4 * getInstance()
    {
        return pInstance4;
    }
};
//直接在给静态成员定义时,创建实例
//为何这里又可以直接调用私有构造函数呢?因为这里是全局环境,不属于任何函数内部调用(如main函数)
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;  //这句相当于调用singleton1()构造函数,只是开辟的栈内存,也不能外部调用构造函数
    //singleton1 *ps1_3 = new singleton1(); //构造函数私有,不能在外部调用
    //singleton1 ps1_3(*ps1_2);     //拷贝赋值操作创建实例副本,该构造函数也设为私有,也不能外部调用
    //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 << "最经典懒汉模式返回函数局部静态实例指针,实例地址:" << ps3_1 << endl;
    cout << "最经典懒汉模式返回函数局部静态实例指针,实例地址:" << ps3_2 << endl;
    
    singleton4 *ps4_1 = singleton4::getInstance();
    singleton4 *ps4_2 = singleton4::getInstance();
    cout << "最经典恶汉模式返回类全局静态实例指针,实例地址:" << ps4_1 << endl;
    cout << "最经典恶汉模式返回类全局静态实例指针,实例地址:" << ps4_2 << endl;
}
  • 如果要设定只能创建5个实例,怎么做?比如限制某个窗口应用程序只能最多开5个窗口
    //0、只适合于懒汉模式,在需要的时候再创建实例
    //1、增加一个类的私有静态成员变量static int instanceCount和一个静态对象指针数组
    //2、在getInstance()函数中判断instanceCount>=5,如果没有的话则new创建实例,并且instanceCount++,然后返回实例指针
    //3、如果判断的实例已经超过了5个,则返回空指针或者返回静态对象指针数组中的一个对象指针

  • 如果要增加析构函数,释放类实例并关闭打开的其他资源文件怎么办?
    //1、增加私有的析构函数,并在析构函数里写上关闭资源文件的语句
    //2、增加public的deleteInstance()函数,里面只写两句:delete pInstence; pInstance = NULL; 即将唯一new的实例释放,
    //则会先自动调用析构函数关闭其他资源,然后才释放开辟的内存

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 单例模式 什么是单例模式?我就不多做赘述了。移步至百度百科单例模式。 什么时候使用单例? 单例模式是一个经典的设计...
    小痴_阅读 6,685评论 8 53
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,231评论 4 34
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,339评论 8 265
  • “我觉得你变了” “人是会变的,我们需要往前走” 一 昨天有个姑娘在微信后台和我说,她昨天去见了她的一个初中同学,...
    刘Toyn阅读 303评论 0 2