已同步到公众号[蚂蚁博士军团][码蚁软件] 👇
如何实现一个高质量的单例模式,项目中经常使用,但是你可能不知道
目录
前言:
众所周知,设计模式有23种,其中单例模式是用得非常广泛的,也是很多人经常面试被问到的问题,当然工作中也是经常用到的设计模式,我们一起来聊聊单例模式。
一、作用
单例模式能保证全局有且只有一个实例对象;
二、实现
一般情况下,得控制对象的生成、释放,使其私有化,可以禁用或屏蔽掉一些构造函数、析构函数、拷贝构造函数、重载等号赋值函数等,使其不能被外部直接使用;同时为了保证全局唯一性,得提供一个静态成员变量来保存这个全局实例;为了能让使用者可以拿到对象,还得提供一个公有的静态函数,让使用者可以直接拿到对象。如果采用指针的方式来保存和创建对象,还得考虑添加一个释放函数,防止内存泄露。
总结下来,实现需要处理三点:
1、控制对象生成:私有化构造、析构、拷贝构造、重载等号赋值函数;
2、私有的静态成员变量:保存对象
3、公有的静态成员函数:可以生成对象并返回对象
三、分类
由于需要使用到静态成员变量来保存对象,静态成员变量初始化的内容不同,就可以产生2种单例模式,分别为饿汉单例、懒汉单例;
饿汉单例:即静态成员变量初始化就给内存,用空间换时间;意思是很饿,需要随时可以拿到吃的,顾名思义为饿汉模式;
懒汉单例:即静态成员变量初始化为空,需要使用了再给内存,用时间换空间;意思是现在不饿,等饿了再拿,饿了再去做吃的,故为懒汉单例;
四、实现方式
1、采用指针的方式来保存全局对象
1.1、指针饿汉:
这种实现方式,在饿汉单例里面,不需要考虑太多,直接返回对象指针即可;
#include <iostream>
#include <mutex>
using namespace std;
mutex g_mutex; // 全局互斥锁
class Singleton
{
private:
Singleton(){}
~Singleton(){}
Singleton(const Singleton &t){}
void operator=(const Singleton &t){}
static Singleton *s_obj; // volatile 防止编译器优化,保证从原始内存读数据,而不是从寄存器读取
public:
static Singleton* getInstance() {
return s_obj;
}
void release() {
if (s_obj) {
delete s_obj;
s_obj = nullptr;
}
}
};
Singleton* Singleton::s_obj = new Singleton; // 饿汉单例,空间换时间
int main()
{
Singleton *a = Singleton::getInstance();
Singleton *b = Singleton::getInstance();
cout << a << " " << b << endl;
if (a == b) {
cout << "单例成功" << endl;
}
else {
cout << "单例失败" << endl;
}
a->release();
b->release();
return 0;
}
1.2、指针懒汉
这种实现方式,就得考虑多线程安全,还得考虑防止编译器优化,还得防止异常死锁
安全问题:加上互斥锁即可解决
异常死锁:由于在new的过程中,极有可能会导致异常退出,使得互斥锁没有来得及退出导致的死锁问题,可以考虑使用唯一锁来配合互斥锁处理异常死锁;
性能问题:加上互斥锁之后,每次都要上锁和解锁的话,还是比较费时间的,为了提升性能,可以对指针先判空处理,不为空了,直接返回对象指针了,不需要再去上锁解锁了,大大节约了时间,提升性能;
关键字volatile的神奇之处:由于在多线程new对象的过程中,有可能会在初始化过程中,产生了地址,但是其他线程从寄存器拿到地址去操作去了,为了防止其他线程没有从原始内存中拿数据,加上一个volatile关键字来修饰这个指针,可以起到很好的效果;
代码如下:
#include <iostream>
#include <mutex>
using namespace std;
mutex g_mutex; // 全局互斥锁
class Singleton
{
private:
Singleton(){}
~Singleton(){}
Singleton(const Singleton &t){}
void operator=(const Singleton &t){}
static Singleton * volatile s_obj; // volatile 防止编译器优化,保证从原始内存读数据,而不是从寄存器读取
public:
static Singleton* getInstance() {
// 双重校验锁:保证安全又能提升性能
if (s_obj == nullptr) { // 提升性能,有内存之后,不会再进去上锁解锁
unique_lock<mutex> ul(g_mutex); // 防止异常死锁
//g_mutex.lock();
if (s_obj == nullptr) { // 防止多次new导致的内存泄露
s_obj = new Singleton;
}
//g_mutex.unlock();
}
return s_obj;
}
void release() {
if (s_obj) {
delete s_obj;
s_obj = nullptr;
}
}
};
//Singleton* Singleton::s_obj = new Singleton; // 饿汉单例,空间换时间
Singleton* volatile Singleton::s_obj = nullptr; // 懒汉单例,时间换空间
int main()
{
Singleton *a = Singleton::getInstance();
Singleton *b = Singleton::getInstance();
cout << a << " " << b << endl;
if (a == b) {
cout << "单例成功" << endl;
}
else {
cout << "单例失败" << endl;
}
a->release();
b->release();
return 0;
}
2、采用对象的方式来保存全局对象
直接在静态函数中,使用一个静态局部变量来保存对象,就会很有意思,可以直接返回对象地址。
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton &t) {}
void operator=(const Singleton &t) {}
public:
static Singleton * getInstance() {
static Singleton obj; // 静态局部变量,存放静态存储区
return &obj;
}
};
int main()
{
Singleton *a = Singleton::getInstance();
Singleton *b = Singleton::getInstance();
cout << a << " " << b << endl;
if (a == b) {
cout << "单例成功" << endl;
}
else {
cout << "单例失败" << endl;
}
return 0;
}