单例模式(懒汉单例和饿汉单例)

转载于:http://www.manongjc.com/article/53899.html

==单例模式就是指一个类在整个程序中只有一个实例。==确保一些不需要重复创建的类创建多余的实例。特别是某些工具类,在所有地方使用该类都只需要一个实例。
基本思路就是在单例类内部创建一个静态的自身对象,并自己管理自己。
下面代码用锤子Hammer来表示这个工具,简单的实现如下

1、懒汉模式

懒汉模式就是指铁匠是个懒汉,这个锤子类的实例在没人用的时候,铁匠不去造它,这样做能节约铁匠铺的空间。

#include <QCoreApplication>
#include <QDebug>
///NOTE7单例模式--懒汉单例的线程不安全做法
class Hammer
{
private:
    static Hammer* m_pHammer;
    Hammer() 
    {
    	qDebug() << "make a new hammer!";
    }
    ~Hammer() {}
    Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
    Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
    static Hammer* GetHammer()
    {
        if(m_pHammer == nullptr)
        {
            qDebug() << "make a new hammer!";
            //由于静态变量的资源在程序最后全部统一释放,所以这个new没有对应的delete问题不大,严谨的话也可以加上释放过程
            m_pHammer = new Hammer();
            return m_pHammer;
        }
    }
    void Beat()
    {
        qDebug() << "I can beat";
    }
};

Hammer* Hammer::m_pHammer = nullptr;


int main(int, char **)
{
  Hammer::GetHammer()->Beat();//获取锤子进行敲打
  getchar();
  return 0;
}

上述单例是存在线程安全问题的,比如有两个客人(多线程)同时需要使用锤子,两个客人的访问流程同时走到了锤子是否被实例化那一个if语句中,此时条件都成立,因此就会产生两把不一样的锤子供客人使用,这明显是不符合设计初衷的。可以通过线程操作的一些方法解决,下面再说。

2、饿汉模式

这个模式可以较简单地解决同时有客人光顾的问题。即铁匠是个饿汉,总是担心生意上门了却没锤子可提供,因此铁匠铺开张的时候(类定义时)就把锤子造好等人来用。代码如下:

//饿汉单例
class Hammer
{
private:
    static Hammer* m_pHammer;
    Hammer()
    {
        qDebug() << "make a new hammer!";
    }
    ~Hammer() {}
    Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
    Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
    static Hammer* GetHammer()
    {
        return m_pHammer;//客人访问时直接提供生产好的锤子
    }
    void Beat()
    {
        qDebug() << "I can beat";
    }
};
//先将锤子生产好
Hammer* Hammer::m_pHammer = new Hammer();
int main(int, char **)
{
    Hammer::GetHammer()->Beat();//获取锤子进行敲打
    getchar();
    return 0;
}

可以看出,和懒汉模式唯一的区别就在于是否先生产好锤子。这种方式会使得空间占用变多,如果没人使用就白白浪费了。

3懒汉模式的改进

懒汉模式可以采用线程锁的方式进行改进,在铁匠铺装一个自动锁,每次客人进门时自动把门锁上,等锤子造好了再开锁。这样限制两个客人同时对懒汉铁匠进行访问。

#include <QCoreApplication>
#include <QDebug>
#include<thread>
#include<mutex>
#include <Windows.h>
using namespace std;
//懒汉单例的线程安全做法
class Hammer
{
private:
    static Hammer* m_pHammer;
    static mutex m_Mutex;//加一把锁
    Hammer()
    {
        qDebug() << "make a new hammer!";
    }
    ~Hammer() {}
    Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
    Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
    static Hammer* GetHammer()
    {
        if(m_pHammer == nullptr)//双重判断,如果没锤子,估计会发生客人同时来店的情况,先锁上,然后再看有没有锤子
        {
            m_Mutex.lock();
            if(m_pHammer == nullptr)
            {
                //由于静态变量的资源在程序最后全部统一释放,所以这个new没有对应的delete问题不大,严谨的话也可以加上释放过程
                m_pHammer = new Hammer();
            }
            m_Mutex.unlock();
        }
        return m_pHammer;
    }
    void Beat()
    {
        qDebug() << "I can beat";
    }
};

Hammer*Hammer::m_pHammer = nullptr;
mutex Hammer::m_Mutex;

void Guest1()//客人1需要锤子
{
    Sleep(1);
    Hammer::GetHammer()->Beat();
}

void Guest2()//客人2需要锤子
{
    Sleep(1);
    Hammer::GetHammer()->Beat();
}

int main(int, char **)
{
    thread thread1(Guest1), thread2(Guest2);
    thread1.detach();
    thread2.detach();

    getchar();
    return 0;
}

4更优雅的方案

以上几种方式都有一个初始化顺序问题,如果有两个单例,其中一个包含另一个,当被包含的未被初始化时会出问题。《EffectiveC++》item4提到了这一点,并提出一种优雅的解决方案。原文是:“将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指涉这些对象。”做法如下:

#include <QCoreApplication>
#include <QDebug>
#include<thread>
#include<mutex>
#include <Windows.h>
using namespace std;
class Hammer
{
public:
    static Hammer& GetHammer();
    void Beat()
    {
        qDebug() << "I can beat";
    }
private:
    Hammer()
    {
        qDebug() << "make a new hammer!";
    }
    ~Hammer()
    {

    }
    Hammer(const Hammer&) = delete;  //单例类建议屏蔽拷贝构造函数
    Hammer& operator=(const Hammer&) = delete; //单例类建议屏蔽拷贝运算符
};

Hammer& Hammer::GetHammer()
{
    static Hammer hammer;
    return hammer;
}


void Guest1()//客人1需要锤子
{
    Sleep(1);
    Hammer::GetHammer().Beat();
}

void Guest2()//客人2需要锤子
{
    Sleep(1);
    Hammer::GetHammer().Beat();
}
int main(int, char **)
{
    thread thread1(Guest1), thread2(Guest2);
    thread1.detach();
    thread2.detach();

    getchar();
    return 0;
}

可以看出,该方法既简单又线程安全,推荐单例模式采用这种方法。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值