在这里首先大家应当了解一下设计模式的概念:
设计模式是一套反复使用、多人知晓、经过分类的代码设计经验的总结。如单例模式、工厂模式、观察者模式等等
单例模式是指一个类只能创建一个对象,保证系统中该类只有一个实例,并提供一个可供访问的全局访问点,该实例被所有程序模块共享,其中单例模式又分为了饿汉模式和懒汉模式两种实现方式。
资源共享:避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置等...
控制资源:方便资源之间的互相通信。如线程池等...
基本思想:
饿汉顾名思义就是很饥饿,迫不及待想吃东西,所以这种实现方式就是:无论以后对象会不会用到,程序一启动时就创建一个唯一的实例对象
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class Singleton
{
public:
//提供静态公有方法,可以类名加域名访问,返回对象指针
static Singleton* GetInstance()
{
return m_instance;
}
class GCarbo
{
public:
~GCarbo()
{
cout << "delete instance" << endl;
if (nullptr != m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
private:
//构造函数私有
Singleton(){
cout << "Singleton" << endl;
};
//防拷贝
//C++98
//Singleton(Singleton const&);
//Singleton& operator=(Singleton const&);
//C++11
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton *m_instance;
static GCarbo Carbo;
};
//在程序入口之前完成单例对象的初始化
Singleton* Singleton::m_instance = new Singleton;
Singleton::GCarbo Carbo;
int main()
{
cout << "main begin" << endl;
//均无法创建实例
//Singleton s;
//Singleton* p = new Singleton;
//调用共有静态成员方法
Singleton* p1= Singleton::GetInstance();
Singleton* p2 = Singleton::GetInstance();
if (p1 == p2){
cout << "yes" << endl;
}
else{
cout << "no" << endl;
}
system("pause");
return 0;
}
运行结果:整个程序运行过程中只调用了一次构造函数,并且两个对象为同一个对象,程序结束后自动销毁对象
相反,懒汉模式就是已经懒到极致了,单例实例当首次被引用时才将进行初始化,尽量使资源的利用最大化。如最常见的晚绑定、写时拷贝技术都是这种实现方式。
但是这里要注意一点,不同于饿汉模式GetInstance全局公共访问点仅仅返回一个类对象的指针,因为我们并没有在类外实例化对象,所以我们要对对象的实例化进行判断,没有对象时才能创建一个对象
static Singleton* GetInstance()
{
if (nullptr == m_instance){
m_instance = new Singleton();
}
return m_instance;
}
完整代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
if (nullptr == m_instance){
m_instance = new Singleton();
}
return m_instance;
}
//内嵌垃圾回收类
class CGarbo{
public:
~CGarbo(){
cout << "delete instance" << endl;
if (nullptr != Singleton::m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
//定义一个静态成员变量,程序结束后自动调用其析构函数释放单例对象
static CGarbo Garbo;
private:
//构造函数私有
Singleton() {
printf("Singleton\n");
};
//防拷贝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
//单例对象指针
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
Singleton::CGarbo Garbo;
int main()
{
cout << "main begin" << endl;
cout << Singleton::GetInstance() << endl;
cout << Singleton::GetInstance() << endl;
system("pause");
return 0;
}
运行结果:
这样写看似就可以了,两个实例也创建了相同的对象,真的就可以了嘛?答案是否定的,现在我们在多线程环境下在对这段代码进行演示
我们发现,两个线程竟然创建了两个不同的对象出来,这显然不符合我们单例模式的要求,为什么会出现这种原因呢,假如第一个线程刚刚判断完 m_instance 为 nullptr 开始创建对象,对象还未创建完成,第二个线程也开始判断,此时对象未创建完成,条件满足,也会继续执行对象创建的代码创建对象,所以创建出了两个不同的对象出来。类似情况可以参考一下Linux下的TOCTTOU(time-of-check-to-time-of-use)错误:https://blog.csdn.net/Sun_Life_/article/details/90142854
可能的线程不安全情况举例(数字表示按时间片程序的执行顺序):
情况一(线程不安全):
情况二(编译器自动优化,进行指令重排):
对于情况一解决办法就是我们需要对创建对象的操作加互斥锁,保证操作的原子性,由于加锁后创建对象线程可能阻塞,所以这里我们为了同时保证效率和安全通常会选择 Double-Check 方式加锁
static Singleton* GetInstance()
{
//Double-Check方式加锁保证效率和线程安全
//保证效率
if (nullptr == m_instance){
//保证线程安全
m_mtx.lock();
//正常检查
if (nullptr == m_instance){
m_instance = new Singleton();
}
m_mtx.unlock();
}
return m_instance;
}
但是情况二显然程序还会出现问题,我们在 m_instance 对象前加上 volatile即可,禁止编译器优化,将变量从内存中读取,而不是从寄存器中读取。
整个懒汉模式的完整代码:
class Singleton
{
public:
static volatile Singleton* GetInstance()
{
//Double-Check方式加锁保证效率和线程安全
//保证效率
if (nullptr == m_instance){
//保证线程安全
m_mtx.lock();
//正常检查
if (nullptr == m_instance){
m_instance = new Singleton();
}
m_mtx.unlock();
}
return m_instance;
}
//内嵌垃圾回收类
class CGarbo{
public:
~CGarbo(){
cout << "delete instance" << endl;
if (nullptr != Singleton::m_instance){
delete Singleton::m_instance;
m_instance = nullptr;
}
}
};
//定义一个静态成员变量,程序结束后自动调用其析构函数释放单例对象
static CGarbo Garbo;
private:
//构造函数私有
Singleton() {
//cout原型operator <<() 连续的cout相当于是函数的递归调用过程
//cout.operator<<(c1);
//由于线程的特性会导致输出乱序
printf("Singleton\n");
};
//防拷贝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
//单例对象指针
static volatile Singleton* m_instance;
static mutex m_mtx;
};
volatile Singleton* Singleton::m_instance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
void func(int n)
{
printf("%d: %p\n", n, Singleton::GetInstance());
//cout << Singleton::GetInstance() << " " << n << endl;
}
int main()
{
cout << "main begin" << endl;
thread t1(func, 1);
thread t2(func, 2);
t1.join();
t2.join();
printf("%p\n", Singleton::GetInstance());
printf("%p\n", Singleton::GetInstance());
system("pause");
return 0;
}
结果完全没有问题: