【设计模式】单例设计模式
设计模式概念
设计模式(Design Pattern )是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结 。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路 的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享
。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再
通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
#include <iostream>
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();
std::cout << p1 << std::endl;
std::cout << p2 << std::endl;
std::cout << p3 << std::endl;
return 0;
}
因为单例模式只能实例化出一个对象,所以p1 = p2 = p3。
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取
文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
唯一的实例对象,直到第一次获取它的时候,才产生。
#include <iostream>
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();
std::cout << p1 << std::endl;
std::cout << p2 << std::endl;
std::cout << p3 << std::endl;
return 0;
}
我们把Singleton类中的实例对象改为指针,在外部实例化的时候我们初始化为nullptr, 我们在getInstance()函数中,如果instamce是nullptr的话,就给它new一块内存来实例化一个对象,如果不是空,返回已经实例化的那个对象。
懒汉式单例模式是不是线程安全的呢????
答案是不是的。
std::mutex mtx;
class Singleton
{
public:
static Singleton* getInstance() // 3.获取类的唯一实例对象的接口方法
{
// std::lock_guard<std::mutex> lock(mtx); // 锁的粒度太大了,单线程的时候也要不断加锁解锁,太浪费了
if (instance == nullptr)
{
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
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;
上面的代码使用锁 + 双重判断来保证线程安全。
class Singleton
{
public:
static Singleton* getInstance() // 3.获取类的唯一实例对象的接口方法
{
// std::lock_guard<std::mutex> lock(mtx); // 锁的粒度太大了,单线程的时候也要不断加锁解锁,太浪费了
static Singleton instance;
return &instance;
}
private:
Singleton() // 1.构造函数私有化
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
上面的这段代码,也是在第一次调用getInstance方法的时候才实例化对象,它也是线程安全的,因为函数静态局部变量的初始化,在汇编指令上已经自动添加线程互斥指令了。
在Linux环境中,通过g++编译上面的代码,命令如下:
g++ -o main main.cpp -g
生成可执行文件main,用gdb进行调试,到getInstance函数,并打印该函数的汇编指令,如下: