单例设计模式
单例:整个项目中,有某个或者某些特殊的类,属于该类的对象,只能创建一个,不允许创建多个。
单例模式实现方法
//单例类
class MyCAS {
private:
//构造函数私有化,就不可以实例化对象了
MyCAS() {};
private:
static MyCAS *m_instance; //静态成员变量,用于实例化单例类
public:
//提供的实例化对象接口
static MyCAS *GetInstance() {
if (m_instance == NULL) {
m_instance = new MyCAS();
}
return m_instance;
}
//测试函数
void func() {
cout << "func的测试" << endl;
}
};
//静态成员必须类外初始化
MyCAS *MyCAS::m_instance = NULL;
int main() {
MyCAS *p_a = MyCAS::GetInstance(); //实例化一个对象,返回类(MyCAS)的指针
MyCAS *p_b = MyCAS::GetInstance(); //再次实例化无效,两次返回的是同一个指针
return 0;
}
- 构造函数私有化:
private: MyCAS();
- 提供静态成员变量,存放实例化单例类的指针
static MyCAS *m_instance;
- 提供实例化对象接口
static MyCAS *GetInstance()
,重要的是只有当m_instance == NULL
的时候才会new 一个新对象,这样才能保证只能实例化一个对象。
单例模式下的delete
之前的单例类会new MyCAS,相应的我们必须要对其进行delete,这里是一种类中嵌套类,利用类的析构函数来释放空间的方法:
public:
//提供的实例化对象接口
static MyCAS *GetInstance() {
if (m_instance == NULL) {
m_instance = new MyCAS();
static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
}
return m_instance;
}
class GarbageCollection { //类中套类,用来释放对象
//在析构函数中释放对象
public:
~GarbageCollection() {
if (MyCAS::m_instance) {
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
- 类中嵌套类,定义一个
class GarbageCollection
,在这个类的析构函数中,调用delete。 MyCAS::GetInstance
中定义一个GarbageCollection类的静态变量static GarbageCollection gc
,这里定义静态变量的原因是,静态变量的生命周期是整个程序的生命周期,当程序运行结束的时候,自动会调用他的析构函数释放掉我们申请的空间。
单例设计模式下的共享数据问题
在单例模式下,假设需要在我们自己创建的线程(而不是主线程中)来创建MyCAS这个单例类的对象,这种线程可能不止一个(最少两个),这时,我们需要面临GetInstance()
这种成员函数需要互斥
//线程入口函数
void myThread() {
cout << "我的线程开始执行" << endl;
MyCAS *p_a = MyCAS::GetInstance(); //实例化MyCAS对象,会出现访问冲突的问题
cout << "我的线程执行结束" << endl;
}
int main() {
//创建两个线程都去实例化MyCAS对象
thread mythread1(myThread);
thread mythread2(myThread);
mythread1.join();
mythread2.join();
return 0;
}
解决方案:
在GetInstance()
中加一个互斥量即可:
static MyCAS *GetInstance() {
unique_lock<mutex> mymutex(mymutex); //加一个锁
if (m_instance == NULL) {
m_instance = new MyCAS();
static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
}
return m_instance;
}
但这又带来一个问题,每次调用MyCAS类中成员函数,不可避免的要调用GetInstance获取对象指针,那么每次调用都要加锁,这样效率很低,所以我们要继续修正:
static MyCAS *GetInstance() {
//加锁
if (m_instance == NULL) { //双重锁定(双重检查)
unique_lock<mutex> mymutex(mymutex);
if (m_instance == NULL) {
m_instance = new MyCAS();
static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
}
}
return m_instance;
}
在外层又嵌套了一个if判断,这就是双重锁定,可以提高效率
- 如果if (m_instance != NULL)条件成立,则肯定表示m_instance已经被new过;
- 如果if (m_instance == NULL)条件成立,不代表m_instance一定没被new过,可能是多线程导致还没来及的赋值,就切换了上下文;
PS:这里一定要避免单线程思维,直接顺着理两次if的逻辑,要用多线程思维,程序执行到任意位置都可能被抢占!(双检查锁似乎会带来内存泄露的问题,日后用到时可以查阅一下)
call_once()
call_once()能够保证函数a()只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。
call_once()的第一个参数是一个标记once_flag,第二个参数是函数名a;
这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。
我们用call_once解决上面提出的单例模式中的GetInstance访问可能冲突的问题:
//once_flag标记
once_flag g_flag;
//单例类
class MyCAS {
private:
static void CreateInstance(){ //因为call_once第二个参数只能是函数名,要把代码封装到函数里
if (m_instance == NULL) {
m_instance = new MyCAS();
static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
}
}
public:
//提供的实例化对象接口
static MyCAS *GetInstance() {
//功能已经被封装到CreateInstance函数中
call_once(g_flag, CreateInstance);
return m_instance;
}
};