单例模式汇总篇续

又是一个美好的下午,斜斜的阳光洒在我的键盘上,好吧,其实是个无聊的下午,同学在讲台上讲他的学生管理系统,还是做点儿有意思的事儿吧。

书接上篇,我们已经写了好几种单例模式,现在我们来说说,上面几种单例模式存在的问题。

带GC的单例模式:

看这段代码:

static Singleton * GetInstance()
    {
        if(m_instance == NULL)  
            m_instance = new Singleton();
        return m_instance;
    }

当有多个线程同时调用GetInstance()时,由于线程调度,当线程A执行完if语句后,可能时间片用完,线程A由运行状态->就绪状态,此时刚好线程B也要执行GetInstance(),在线程B时间片用完后,线程A获得时间片,继续执行m_instance = new Singleton(); 所以这种延迟加载的模式存在线程安全的问题。那怎么解决这个问题呢?
我们可以采用双检锁模式(double checked locking pattern),如下:

static Singleton * GetInstance()
    {
        if(m_instance==NULL)
        {
            LOCK();//这个锁需要自己实现
            if(m_instance == NULL)  
                m_instance = new Singleton();
            UNLOCK();//
        }
        return m_instance;
    }

逻辑上,只有当m_instance为空时,我们加锁,构建实例;当m_instance不为空时,直接返回m_instance就行了,所以锁要加在判断语句后面。另外如果我们把锁加在判断语句之前,那么在m_instance非空时,也会频繁的加锁解锁,浪费性能。还有一个问题为什么使用双检锁?下面的代码会存在什么问题?

static Singleton * GetInstance()
    {
        if(m_instance==NULL)
        {
            LOCK();//这个锁需要自己实现
            m_instance = new Singleton();
            UNLOCK();//
        }
        return m_instance;
    }

其实这样写也是会存在线程安全的问题,因为有可能会有多个线程同时通过if(m_instance==NULL),所以我们需要多一层检查。采用智能指针的线程安全方案于此同理。

Meyers Singleton:

Meyers大师采用的是局部静态对象,局部静态对象的初始化在C++0X与C++11有很大的差异。

首先我们来看一下在C++0X中:

局部静态变量在编译时,编译器的实现一般是在初始化语句之前设置一个局部静态变量的标识来判断是否已经初始化,运行的时候每次进行判断,如果需要初始化则执行初始化操作,否则不执行。这个过程本身不是线程安全的. 根据这段描述,我们来写一段伪代码:

static Singleton& GetInstance()
{
    static bool isInit = false;
    static Singleton m_instance;//(未初始化的m_instance)
    if(isInit==false)
    {
        m_instance = *(new Singleton());//new 返回的是指针,需要解引用
    }
    return m_instance;
}

在上面的伪代码中,我们一目了然,这种模式也是非线程安全的。解决办法可以采用上面提到的双检锁。

再来看看在C++11中:

C++11标准规定了局部静态变量初始化需要保证线程安全,具体说明如下:If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialisation。–如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待完成该变量完成初始化。所以在C++11中,Meyers Singleton不仅是代码量最少,而且是线程安全的。

饿汉模式:

上面几种都是懒汉模式,延迟加载,下面我们来看饿汉模式:

class Singleton
{
public:
    static std::shared_ptr<Singleton> GetInstance()
    {
        //if(m_instance == NULL)
        //    m_instance = std::shared_ptr<Singleton>(new Singleton);
        return m_instance;
    }
public:
    ~Singleton()//必须是public 由智能指针调用
    {
        std::cout<<"destructor has been called\n";
    }
private:
    static std::shared_ptr<Singleton> m_instance;
    Singleton()
    {
        std::cout<<"constructor has been called\n";
    }
};
std::shared_ptr<Singleton> Singleton::m_instance = std::shared_ptr<Singleton>(new Singleton);

这里直接使用了智能指针,实现自动回收。
类的静态成员变量在main函数之前进行初始化,所以当调用GetInstance()时,单例实例已经存在,所以不会有线程安全问题,饿汉模式是线程安全的。
饿汉模式好像很完美啊,其实有一个比较傻x的隐患,例如有两个单例类,在单例A的构造函数里调用单例B的方法,
伪代码:

A()
{
    B::GetInstance()->do();
}

饿汉模式的对象是在类加载的时候构建的,如果A执行构造函数的时候,B还没有加载,那么就会出错。解决办法是当我们使用饿汉模式时要避免这种写法。。。
到此闲话单例模式说完~~~,欢迎补充~

ps:刚刚上讲台,台下一脸懵逼,我也是呵呵哒了~~~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值