c语言设计模式之单例模式中的饿汉与懒汉详解
设计模式
设计模式是一套反复使用、多人知晓、经过分类的代码设计经验的总结。
如单例模式、工厂模式、观察者模式等等。
单例模式
单例模式是指一个类只能创建一个对象,保证系统中该类只有一个实例,并提供一个可供访问的全局访问点,该实例被所有程序模块共享,其中单例模式又分为了饿汉模式和懒汉模式两种实现方式。
应用
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
资源共享:
避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置等...
控制资源:
方便资源之间的互相通信。如线程池等...
实现
基本思想:
构造函数为private私有属性全局唯一公共访问点来创建对象,函数为public属性为了使用方便增加一个GC辅助类来释放资源
饿汉模式
饿汉顾名思义就是很饥饿,迫不及待想吃东西,所以这种实现方式就是:无论以后对象会不会用到,程序一启动时就创建一个唯一的实例对象
- 优点:简单,线程安全
- 缺点:可能导致进程启动慢、多个单例对象实例启动顺序不可控
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> 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 <iostream> 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 开始创建对象,对象还未创建完成,第二个线程也开始判断,此时对象未创建完成,条件满足,也会继续执行对象创建的代码创建对象,所以创建出了两个不同的对象出来。
可能的线程不安全情况举例(数字表示按时间片程序的执行顺序):
情况一(线程不安全):
情况二(编译器自动优化,进行指令重排):
对于情况一解决办法就是我们需要对创建对象的操作加互斥锁,保证操作的原子性,由于加锁后创建对象线程可能阻塞,所以这里我们为了同时保证效率和安全通常会选择 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; }
结果完全没有问题:
到此这篇关于c语言设计模式之单例模式中的饿汉与懒汉详解的文章就介绍到这了,更多相关c语言单例模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
C++示例讲解friend static const关键字的用法
静态成员static是解决同一个类的不同对象之间数据和函数共享问题。区分全局变量,全局变量也能实现数据共享,但安全性和封装性被破坏了,友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制,const常引用-被引用的对象不能被更新2022-06-06
最新评论