目录
- 单例模式简介
- 饿汉单例模式
- 懒汉单例模式
- 线程安全的懒汉单例模式
橙色
详细可参考该篇文章:C++设计模式 - 单例模式
单例模式简介
单例模式指的是,无论怎么获取,永远只能得到该类类型的唯一一个实例对象,那么设计一个单例就必须要满足下面三个条件:
1、构造函数私有化,这样用户就不能任意定义该类型的对象了
2、定义该类型唯一的对象
3、通过一个static静态成员方法返回唯一的对象实例
饿汉单例模式
饿汉式单例模式,顾名思义,就是程序启动时就实例化了该对象,并没有推迟到第一次使用该对象时再进行实例化
为什么getInstance成员函数要加static关键字?因为普通成员方法的调用还是要依赖于对象,但现在没对象,现在是通过接口来获取对象的,所以 返回唯一的对象实例的函数 应该是静态成员函数。
下面这个是饿汉式单例模式:
#include <iostream>
using namespace std;
/*
单例模式:
饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了
懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候,才产生
*/
//饿汉式单例模式,一定是线程安全的
class Singleton
{
public:
static Singleton* getInstance()//#3 获取类的唯一实例对象的接口方法
{
return &instance;
}
private:
static Singleton instance;//#2 定义一个唯一类的实例对象
Singleton() //#1 构造函数私有化
{
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
cout << p1 << " " << p2 << " " << p3 << endl;
return 0;
}
但有时呢,可能你整个任务过程中都没有试图获取过该单例对象,但函数是先于程序执行载入的,那么可能饿汉模式,先定义了一个唯一的实例对象,并且构造函数进行了大量的初始化,所以在程序启动时就会先创建出一个实例化对象,白白浪费很多时间。所以有时就会用懒汉模式
懒汉单例模式
下面这个是懒汉式单例模式,把对象的实例化延迟到第一次获取该实例对象时。在程序启动时,它仅仅先创建了一个指针而不是一个对象
可重入函数:在多线程中能够被不同线程重复调用,即一个线程调用还没结束时,另一个线程又来调用,这样的函数就称为可重入函数
#include <iostream>
using namespace std;
/*
单例模式:
饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了
懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候,才产生
*/
//懒汉式单例模式,存在线程安全的问题
class Singleton
{
public:
static Singleton* getInstance()//#3 获取类的唯一实例对象的接口方法
{
if(instance==nullptr)
{
instance = new Singleton();
}
return instance;
}
private:
static Singleton *instance;//#2 定义一个唯一类的实例对象
Singleton() //#1 构造函数私有化
{
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton&) = delete;
};
Singleton* Singleton::instance=nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
cout << p1 << " " << p2 << " " << p3 << endl;
return 0;
}
线程安全的懒汉单例模式
饿汉单例模式中,单例对象定义成了一个static静态对象,它是在程序启动时,main函数运行之前就初始化好的,因此不存在线程安全问题,可以放心的在多线程环境中使用。但上面的懒汉模式代码却不是安全的
在静态成员变量instance的前面加volatile关键字,是因为在多线程中cpu为了加快速度,往往会将静态成员变量的值都拷贝一份,带到各个的线程中去,放到cpu的缓存里。而加了volatile,加了该关键字后,就禁止了各个线程拷贝该变量。如果该静态成员变量的值发生改变,那么所有的线程都会立刻看到静态成员变量的改变
下面是线程安全的懒汉单例模式:
#include <iostream>
#include<mutex>
using namespace std;
/*
单例模式:
饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了
懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候,才产生
*/
mutex mtx;
//懒汉式单例模式=》是不是线程安全的呢?=》线程安全的懒汉式单例模式
class Singleton
{
public:
//是不是可重入函数呢? 锁+双重判断
static Singleton* getInstance()//#3 获取类的唯一实例对象的接口方法
{
//lock_guard<mutex> guard(mtx);//在这里加锁锁的粒度太大了,如果是单线程的话,调用该函数还是要每次加锁解锁
if(instance==nullptr)
{
lock_guard<mutex> guard(mtx);
if(instance==nullptr)
{
/*
开辟函数
构造函数私有化
给instance赋值
*/
instance = new Singleton();
}
}
return instance;
}
private:
static Singleton *volatile instance;//#2 定义一个唯一类的实例对象
Singleton() //#1 构造函数私有化
{
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton&) = delete;
};
Singleton*volatile Singleton::instance=nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
cout << p1 << " " << p2 << " " << p3 << endl;
return 0;
}
当然,线程安全的懒汉模式还有另一种写法,如下:
#include <iostream>
#include<mutex>
using namespace std;
/*
单例模式:
饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了
懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候,才产生
*/
//懒汉式单例模式
class Singleton
{
public:
//仅在第一次调用该函数的时候,才会创建该对象
static Singleton* getInstance()//#3 获取类的唯一实例对象的接口方法
{
//函数静态局部变量的初始化,在汇编指令上已经自动添加了线程互斥指令了
static Singleton instance;
return &instance;
}
private:
Singleton() //#1 构造函数私有化
{
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton&) = delete;
};
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
cout << p1 << " " << p2 << " " << p3 << endl;
return 0;
}