单例模式
- 保证一个类只有一个实例,并提供一个访问它的全局访问点
单例模式实际开发过程中使用频率还是挺高的,以前就那么用,只知道有懒汉式,饿汉式,也没有去深究,今天趁着学习设计模式,总结下。
好,咱们直入正题,先来说说懒汉式。
-
懒汉式
懒汉嘛,懒这个字就说明了你不去管他,他就不会动,放在单例模式里面,那就是:你不去调用他,他就不会去实例化那唯一的对象,示例代码如下:
class lazySingle
{
private:
static lazySingle* la;
public:
static lazySingle* GetSingle()
{
if(la == NULL)
la = new lazySingle;
return la;
}
void testFun()
{
cout << "This is a test fun" << endl;
}
private:
lazySingle() {}
lazySingle(lazySingle &l) {}
~lazySingle() {}
};
//在这里提醒下,一定记得初始化静态变量哦
lazySingle* lazySingle::la = NULL;
这个办法有一个显而易见的问题,那就是线程不安全,具体的可以参考以下场景:
有这么两个线程,线程中同时调用lazySingle::GetSingle()(假设在这之前没有任何人调用过这个函数)来获取单例模式中唯一的实例,第一个线程判断la==NULL,同时第二个线程也判断la==NULL,这个时候,两个线程得到的指针指向的并不是同一个实例,这也就违背了单例模式“保证一个类只存在一个实例”。
解决的办法也有啊,线程安全首先想到的是互斥锁,那么加一把锁好了。
class lazySingle
{
private:
static lazySingle* la;
static mutex lock;//这把锁为什么是static呢?嗯...因为他要在static里面用啊
public:
static lazySingle* GetSingle()
{
lock.lock();
if(la == NULL)
la = new lazySingle;
lock.unlock();
return la;
}
void testFun()
{
cout << "This is a test fun" << endl;
}
private:
lazySingle() {}
lazySingle(lazySingle &l) {}
~lazySingle() {}
};
lazySingle* lazySingle::la = NULL;
mutex lazySingle::lock;
算是解决了问题了,可是好麻烦啊,有没有不需要考虑线程安全的单例模式?有!那就是饿汉式。顾名思义,饿汉式就是说,我怕饿,别管我现在用不用,你先把东西给我准备好。
-
我的饿汉式1
这是我第一次使用饿汉式写单例模式,当时也不是什么具体项目,就是自己写个demo练练手,那是怎么省事儿怎么来,网上一顿查(前后不超过五分钟),奥,原来这么写,简单,来呗。
class HungrySingleA
{
public:
static HungrySingleA* GetSingle()
{
static HungrySingleA hu;
return &hu;
}
private:
HungrySingleA()
{
cout << "HungrySingleA()" << endl;
}
HungrySingleA(HungrySingleA& h){}
~HungrySingleA(){}
};
大致上就这么个模式,具体肯定没这么简单啦。看着没啥问题是吧?可是有一天我突然想到一个问题:
静态局部变量在第一次调用的时候才进行初始化,那么如果两个线程同时调用(在此之前没有被调用过),这么时候是不是就会出现上述懒汉式会遇到的问题:得到两个不一样的实例?我不知道啊,这个时候只能感叹自己知识的匮乏,然后上网去找资料...
https://blog.csdn.net/imred/article/details/89069750
(这里面描述了一个有趣的现象:初始化时发生了对初始化的递归调用,说实话我不觉得谁会这么干...)
这是我找到一篇博客,开头第一句:“在C++11标准中,要求局部静态变量初始化具有线程安全性”,看到这里我松了口气,剩下部分我都不需要去看了(感兴趣的可以看看),因为我现在大部分时候使用的正是c++11标准。可是,公司有很多老代码,他们的运行环境是vs2010甚至更低,他们不支持c++11,他还是线程安全的吗?那么好吧,为了安全,继续改造。
-
我的饿汉式2
class HungrySingleB
{
public:
static HungrySingleB* GetSingle()
{
return &hsb;
}
private:
HungrySingleB()
{
cout << "HungrySingleB()" << endl;
}
HungrySingleB(HungrySingleB& h){}
~HungrySingleB(){}
static HungrySingleB hsb;
};
HungrySingleB HungrySingleB::hsb;
这样在一开始单例模式中的“单例”在一开始就是存在的,不管谁去获取它,始终是那一个实例。
基本单例模式就是这样了。