前言:
目录
(一)设计模式的六⼤原则
(二)设计模式的分类
(三)单例模式
1、定义
2、实现方式
1️⃣ 懒汉模式
2️⃣ 饿汉模式
(四)懒汉模式的安全实现
总结
首先我们需要知道的是设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路。它不是语法规定,⽽是⼀套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。
单⼀职责原则(Single Responsibility Principle)
开闭原则(Open Closed Principle)
⾥⽒替换原则(Liskov Substitution Principle)
依赖倒置原则(Dependence Inversion Principle)
迪⽶特法则(Law of Demeter),⼜叫“最少知道法则”
接⼝隔离原则(Interface Segregation Principle)
从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体到每⼀条设计原则,则对应⼀条注意事项:
设计模式可以根据其目的和使用方式进行分类。以下是常见的设计模式分类:
创建型模式(Creational Patterns):这些模式关注对象的创建过程,用于实例化对象的方式。常见的创建型模式包括:
结构型模式(Structural Patterns):这些模式关注对象之间的组合和关联方式,以形成更大的结构。常见的结构型模式包括:
行为型模式(Behavioral Patterns):这些模式关注对象之间的通信和交互方式,以定义对象之间的责任分配和行为。常见的行为型模式包括:
除了这些主要的分类,其实还有:并发模式和线程池模式。
本期,我们先学习设计模式中的第一种模式——单例模式。
⼀个类只能创建⼀个对象,即单例模式,该设计模式可以保证系统中该类只有⼀个实例,并提供⼀个访问它的全局访问点,该实例被所有程序模块共享。⽐如在某个服务器程序中,该服务器的配置信息存放在⼀个⽂件中,这些配置数据由⼀个单例对象统⼀读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种⽅式简化了在复杂环境下的配置管理。
单例模式通常有两种模式,分别为懒汉式单例和饿汉式单例。两种模式实现方式分别如下:
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好!!
对于懒汉模式常见的有两种设计方法:
(1)懒汉模式实现一:静态指针 + 用到时初始化
template
class Singleton
{
public:
static T& getInstance()
{
if (!_value)
{
_value = new T();
}
return *_value;
}
private:
Singleton()
{}
~Singleton()
{}
static T* _value;
};
template
T* Singleton::_value = NULL;
【解释说明】
在单线程中,这样的写法是可以正确使用的,但是在多线程中就不行了,该方法是线程不安全的。
另外,还存在内存泄漏的问题,new出来的东西始终没有释放,下面是一种饿汉式的一种改进。
template
class Singleton
{
public:
static T& getInstance()
{
if (!_value)
{
_value = new T();
}
return *_value;
}
private:
// 实现一个内嵌垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (Singleton::_value)
delete Singleton::_value;
}
};
static CGarbo Garbo;
Singleton()
{};
~Singleton()
{};
static T* _value;
};
template
T* Singleton::_value = nullptr;
【解释说明】
使用这种方法释放单例对象有以下特征:
【注意】
getInstance()
,有可能会创建多个对象。为了实现线程安全的单例模式,需要使用适当的同步机制,例如使用互斥锁或双重检查锁定(这个下面会讲到)(2)懒汉模式实现二:局部静态变量
template
class Singleton {
public:
static T& getInstance() {
static T instance;
return instance;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};
【解释说明】
Singleton::getInstance()
调用 getInstance()
函数来获取到 YourClass
类的单例对象。delete
关键字,而是通过私有化拷贝构造函数和赋值操作符来限制拷贝行为,从而实现单例的唯一性。程序启动时就会创建⼀个唯⼀的实例对象。因为单例对象已经确定,所以⽐较适⽤于多
线程环境中,多线程获取单例对象不需要加锁,可以有效的避免资源竞争,提⾼性能。
(1)饿汉模式实现一:直接定义静态对象
template
class Singleton
{
private:
static T _eton;
private:
Singleton() {}
~Singleton() {}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static T& getInstance()
{
return _eton;
}
};
template
T Singleton::_eton;
优点:
缺点:
使用条件:
(2)饿汉模式实现二:静态指针 + 类外初始化时new空间实现
template
class Singleton
{
private:
static T* _eton;
Singleton() {}
~Singleton() {}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static T& getInstance()
{
return *_eton;
}
};
template
T* Singleton::_eton = new T();
【小结】
class Singleton
{
public:
static Singleton* GetInstance()
{
// 双检查加锁
if (m_pInstance == nullptr) {
m_mutex.lock();
if (m_pInstance == nullptr)
{
m_pInstance = new Singleton;
}
m_mutex.unlock();
}
return m_pInstance;
}
static void DelInstance()
{
m_mutex.lock();
if (m_pInstance)
{
delete m_pInstance;
m_pInstance = nullptr;
}
m_mutex.unlock();
}
// 实现一个内嵌垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
DelInstance();
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
// 一般全局都要使用单例对象,所以单例对象一般不需要显示释放
// 有些特殊场景,想显示释放一下
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_vmtx.unlock();
}
~Singleton()
{
// 持久化
// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
}
private:
mutex _vmtx;
vector _v;
private:
// 构造函数私有
Singleton()
{}
// 防拷贝
//Singleton(Singleton const&);
//Singleton& operator = (Singleton const&);
static mutex m_mutex; //互斥锁
static Singleton* m_pInstance; // 单例对象指针
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mutex;
int main()
{
srand(time(0));
int n = 3000;
thread t1([n]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
}
});
thread t2([n]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
}
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
return 0;
}
到此,关于单例模式的讲解便到此结束了。接下来,简单的回顾总结一下本文!!!
懒汉模式和饿汉模式都是单例模式的实现方式,都用于确保一个类只有一个实例,并提供全局访问点。
在实际应用中根据具体情况权衡懒汉模式和饿汉模式的优缺点,并选择适合的实现方式。
以上便是本文的全部内容,感谢大家的观看和支持!!!