单例模式
很多时候,我们在程序设计的时候,对于某一类只需要生成其中一个对象。比如说对于MySQL数据库类来说,如果频繁的创建MySQL对象,那么会有海量的连接连接到MySQL数据库,对数据库来说是一个不小的负担。
但其实对于开发者来说,一个连接其实就能胜任与数据库的交互工作,这就是单例模式。
两种单例模式
而对于单例模式来说,又分为两种:懒汉式、饿汉式
两种单例模式:
懒汉式: 当获取实例对象时,对象才产生
饿汉式: 还没有获取实例对象时,对象已经产生了
饿汉式
可能大家已经想到了,利用static修饰的变量属于类这一特性来实现。确实也是这么做的:
1.构造函数私有化
2.定义static修饰的唯一实例对象
3.类外初始化
4.类中提供一个供外界获取的static接口
class Singleton
{
public:
static Singleton* get_instance()
{
return &instance;
}
private:
static Singleton instance; //唯一对象
Singleton() //构造函数私有化
{
}
Singleton(const Singleton&) = delete;
Singleton operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;
这里我们可以看到,无论是多线程还是单线程,对于饿汉式来说,都是线程安全的。
懒汉式
既然有了饿汉式,而且饿汉式在多线程方面还是近乎完美的,那么又要懒汉式单例模式干什么呢?
设想一种情况:一个项目中有很多需要单例设计的类,如果其中大部分类呢又用不到,但是饿汉式会让他们都生成实例对象,这样就造成了内存资源一种浪费。
所以就需要懒汉式设计模式:
1.构造函数私有化
2.类中设计一个static修饰的指针并在类外初始化
3.提供一个供外部访问的static修饰的函数接口
4.函数接口中实现new一个实例对象
mutex g_mutex;
class Singleton
{
public:
static Singleton* get_instance()
{
if (instance == nullptr)
{
lock_guard<mutex> lock(mutex);
if (instance == nullptr)
{
instance = new Singleton();
}
}
return instance;
}
private:
static Singleton *volatile instance; //唯一对象指针
//volatile 编译器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份
Singleton() //构造函数私有化
{
}
Singleton(const Singleton&) = delete;
Singleton operator=(const Singleton&) = delete;
};
static Singleton* volatile instance = nullptr; //唯一对象
可以看到,我这里是加了一把锁g_mutex
的,而且在对外提供的static函数接口内实现了双重 if
判断,这是因为,如果不加锁的话:
static Singleton* get_instance()
{
if (instance == nullptr)
{
instance = new Singleton();
}
return instance;
}
试想一种情况:如果两个线程同时进入get_instance函数,并且此时他们进入时的instance都是nullptr,那么这两个线程都会new一个Singleton对象,但是instance只能去指向其中的一个,这样就会造成有一个Singleton的内存泄漏。
而在锁后又加一层 if 判断的原因是:
if (instance == nullptr)
{
lock_guard<mutex> lock(mutex);
if (instance == nullptr)
{
instance = new Singleton();
}
}
还是两个线程,进入都判断instance为nullptr,都通过了第一个if,其中线程A获得锁,创造了一个Singleton对象,然后释放锁;线程B 获得锁,如果不加第二个 if 的话无疑会再次创造一个实例对象,这是我们不希望看见的。
参考文献
[1] 施磊.C++高级.图论科技.2020.7.