单例-怎么用单例

单例的实现

在实现一个单例时,需要从如下角度来考虑单例的实现是否合理:
1.不应该提供创建多个实例的接口。主要是两部分内容,分别是要将构造函数设为private属性,防止在别处通过new创建实例;在创建实例时要考虑线程安全问题,避免在多线程环境中运行时,同一时间多个线程竞争,导致创建了多个实例。
2.考虑是否支持延迟加载。根据系统性能要求的不同,有的系统要求启动速度快,那就不能在启动时创建实例;有的系统要求获取实例时快,那就不能在调用单例时创建实例。
3.考虑Getinstance性能是否高(是否加锁),如果每次获取单例都要加锁解锁,那么对性能的影响是很大的。

饿汉式

懒汉式单例从生命周期来讲,是“与天地同寿的”,在调用main函数之前,单例的构造函数就已经被调用必完成了初始化。(在msvc C++中,真正的函数入口是msvcStartup而不是main函数,在调用main函数之前,
会执行全局对象的初始化,具体实现是这样的:在编译时,将全局构造函数的指针放在一个特殊的段(.ctors段)中,在调用msvcStartup时,遍历该段,将里面所有的构造函数执行一遍)。具体实现如下:

//Signleton.h
class Signleton
{
public:
    virtual ~Signleton();
    Signleton& Getinstance();

private:
    Signleton();//先藏起构造函数
};

//Signleton.cpp
Signleton instance;//这个是放在cpp文件里面的全局变量。

Signleton& Signleton::Getinstance() {
    return instance;//不需要加锁因为在系统构造前就建好了实例
}

饿汉式由于是在系统启动之前就存在了,显然是不支持延迟加载的。与此同时,由于先于系统启动,也不需要
但是,C++中,全局构造函数的调用顺序是未知的,也就是说,如果这个单例中构造时依赖了其他单例,那这个行为就是未定义的。其次,如果单例构造时需要用到系统的一些参数,那这些参数是无法获得的,因为系统都还没启动。。。
那么,饿汉式需不需要考虑构造时线程问题呢?不用的,肯定是在主线程执行的时候由主线程创建的,没别的线程来竞争。
那支持延时加载吗?不支持的,他比谁都先加载出来
那需要加锁吗?不需要的,因为不管多少个线程同时调用GetInstance,Getinstance返回的结果都是一样的,都是系统启动前加载的那个。

懒汉式

懒汉式恰恰和饿汉式相反,生存周期可以说是“应运而生”,就是说只有在第一次调用Getinstance时才会被实例化。具体实现如下:

//Signleton.h
class Signleton
{
public:
    virtual ~Signleton();
    Signleton& Getinstance();

private:
    Signleton();//先藏起构造函数
private:
    Signleton* mInstance;
};

//Signleton.cpp
Signleton* Signleton::mInstance = nullptr;

Signleton& Signleton::Getinstance() {
    if (nullptr == mInstance) {
        mInstance = new Signleton;
    }
    return *mInstance;
}

那么,还是使用你是三个问题来评判懒汉式的实现:
首先是是否支持多线程环境?答案是不支持的,在多线程环境下,如果两个线程th1th2同时执行并且都执行到了 nullptr == mInstance这个判断的时候,th1th2将同时、分别生成两个实例instance1instance2。所以懒汉式不支持多线程环境
其次是判断懒汉式是否支持延迟加载?由于是在第一次调用的时候实例化的,满足延迟加载的条件。

双重检查式

为了满足在多线程环境下运行的需求,现在对懒汉式实现打点补丁。具体实现如下:

//Signleton.h
class Signleton
{
public:
    virtual ~Signleton();
    Signleton& Getinstance();

private:
    Signleton();//先藏起构造函数
private:
    Signleton* mInstance;
    mutex mMutex;
};

//Signleton.cpp
Signleton* Signleton::mInstance = nullptr;

Signleton& Signleton::Getinstance() {
    if (nullptr == mInstance) {//注意这里是判断了两次
        mMutex.lock();
        if (nullptr == mInstance) {
            mInstance = new Signleton;
        }
        mMutex.unlock();
    }
    return *mInstance;
}

打了补丁之后的懒汉式单例,既可以支持延时加载,又可以在多线程环境下运行。在调用
GetInstance的时候,先判断是否已经实例化,如果没有实例化,就加锁,并且实例化。实例化的时候,其他线程调用mMutex.lock()将会被阻塞住。实现在多线程环境下只实例化一个对象的目的。
可是,为什么是两次判断?Getinstance像下面实现不好吗?

Signleton& Signleton::Getinstance() {
        mMutex.lock();
        if (nullptr == mInstance) {
            mInstance = new Signleton;
        }
        mMutex.unlock();
    return *mInstance;
}

这样的话,每次调用Getinstance都会加锁解锁,开销很大,降低了系统效率。
如果要提升系统效率,像下面那样实现行不行?

Signleton& Signleton::Getinstance() {
    if (nullptr == mInstance) {
        mMutex.lock();
        mInstance = new Signleton;
        mMutex.unlock();
    }
    return *mInstance;
}

其实也不行,这样也会存在同时多个线程通过nullptr == mInstance判断的情况,这种写法根本不能支持多线程环境,具体点说就是线程th1,th2同时都通过了外层判断到了内层,此时th1拿到锁,th2阻塞,当th1实例化完成时,th2就会直接再新建一个实例并返回,就是说Signleton被实例化了两次。
所以,两次判断,也是有两个目的的,首先,要在没有实例化时,实例化对象,通多外层判断加快第二次调用Getinstance的性能。其次,内层判断,是要防止当前线程拿到锁的那一刻,别的线程没有实例化对象完成。

局部变量式

从效果上来看,双重加锁仿佛已经是无敌了,及支持延时加载,也可以支持多线程并发,同时加锁也仅仅是在第一次调用,效率较高。还有一种写法,原理上和双重加锁类似,但是更加简洁,具体实现如下:

Signleton& Signleton::Getinstance() {
        static Singleton value;  //静态局部变量
        return value;
    }

上面的式子,利用了局部静态变量在第一次被调用时就会初始化的特点,而且C++11标注明确规定了静态变量的实例化是线程安全的,所以说只有第一次加载且线程安全这两个条件,都利用C++自身的特性实现了,所以我说思想和双重检测类似,但是写法更简单。

(0)

相关推荐

  • C ++:单例

    介绍 单身人士几乎不需要介绍.一个搜索CodeProject上单独变成了关于他们的50余篇. 那为什么还要写另一篇文章呢?好吧,各种各样的问题不断涌现,我想讨论一下.关于稳健服务核心(RSC)实现单例 ...

  • 教你如何诊断功能单心室:功能单心室( A 型,右侧房室瓣闭锁)一例

    ⊙作者 /  赵卉霖 ⊙单位 /  河北医科大学第二医院 图 1 图 2 图 1 .图 2 四腔心切面:右侧房室瓣区(三尖瓣)呈带状强回声(箭头所示),未见瓣膜结构回声,显示左后方的单心室主腔(左室) ...

  • MOG抗体介导的单侧皮质脑炎1例报告

    近年来, 关于髓鞘少突胶质细胞糖蛋白 (myelin oligodendrocyte glycoprotein, MOG) 抗体在中枢神经系统炎性脱髓鞘疾病 (idiopathic inflammat ...

  • 良性单侧下肢萎缩一例

    病例报告 患者女性,34岁,主因 "左下肢小腿肌萎缩伴轻度无力麻木10年.左下肢酸胀2个月"于2015-08-27入作者医院.患者于2005-06无明显诱因感左下肢轻度的无力及麻木 ...

  • 缅甸疫情卷土重来!单天暴增17例,15例境外输入2例本土感染

    [缅甸中文网讯]8月20日早上8点,卫生与体育部更新疫情信息显示,8月19日,各个卫生化验室累计完成了1664份样本的化验检测工作.8月19日晚,发现15例新增病例,均为境外输入病例:昨晚到今天凌晨, ...

  • 美国新增确诊45230例、死亡792例

    2021-05-08 06:39:27  来源:观察者网 (观察者网讯)根据美国约翰斯·霍普金斯大学统计数据,截至北京时间2021年5月8日5时20分,美国新冠肺炎累计确诊病例32643635例,累计 ...

  • 淘宝商家在哪里补单?补单平台怎么补单有效果?

    今天跟大家分享的主要内容是淘宝商家在哪里补单?怎么补单有效果.补单其实很多买家都要做的,但是补单怎么做很多却不知道,那么下面大文就带大家去了解一下. 我们已经能够将产品进行了一定了测款,现在就是要来进 ...

  • 美国新增确诊37774例、死亡694例

    2021-05-09 07:04:15 来源:观察者网 (观察者网讯)根据美国约翰斯·霍普金斯大学统计数据,截至北京时间2021年5月9日5时20分,美国新冠肺炎累计确诊病例32681409例,累计死 ...

  • 美国新增确诊23188例、死亡243例

    2021-05-10 06:49:57 来源:观察者网 (观察者网讯)根据美国约翰斯·霍普金斯大学统计数据,截至北京时间2021年5月10日5时20分,美国新冠肺炎累计确诊病例32704597例,累计 ...

  • 首次 越南新增太多午间多一次通报 单日确诊129例 累计确诊3461例

    今天(2021年5月10日),越南卫生部第一次开通中午12点新冠疫情通报,这是越南卫生部从新冠疫情爆发以来第一次发布中午12点的疫情通报.此前,每天的疫情通报只有2次,分别是上午6时和下午18时.从今 ...