网站首页 > 技术文章 正文
单例模式概述
单例模式是指:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。在实际的开发中,会经常遇到一个类只能创建一个对象。如果有多个对象,可能会导致状态的混乱。这种情况下,单例模式是最恰当的解决方法。例如多个进程都想调用打印机接口打印数据,这时只能创建一个打印机实例;如果不同的进程都创建独自的打印机实例,最终会出现打印任务冲突问题。通过单例模式可以做到:
- 确保一个类只能创建一个实例。
- 提供了一个对对象的全局访问指针。
- 在不影响单例类的客户端的情况下,允许将来有多个实例。
单例模式结构
单例模式结构非常简单,其UML图如下所示,只包含一个类,即单例类。为防止创建多个对象,其构造函数必须是私有的(外界不能访问)。另一方面,为了提供一个全局访问点来访问该唯一实例,单例类提供了一个公有方法getInstance来返回该实例。
单例模式通常有三种形式,分别是懒汉式、饿汉式和多线程式。
懒汉式
懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance () 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。
#include <iostream>
using namespace std;
class Singleton
{
public:
~Singleton()
{
std::cout << "~Singleton" << std::endl;
}
static Singleton* get_instance()
{
if (m_pInstance == nullptr)
{
m_pInstance = new Singleton;
}
return m_pInstance;
}
void use() { cout << "use singleton" << endl; }
private:
Singleton()
{
std::cout << "Singleton" << std::endl;
}
Singleton(Singleton&) = delete;//禁止调用该函数
Singleton& operator=(const Singleton&) = delete;//禁止调用该函数
static Singleton* m_pInstance;
};
Singleton* Singleton::m_pInstance = nullptr;
int main()
{
Singleton* Instance1 = Singleton::get_instance();
Singleton* Instance2 = Singleton::get_instance();
return 0;
}
可以看到,获取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例。
懒汉式缺点:
- 线程安全的问题,当多线程获取单例时有可能引发竞态条件:线程1在 if 中判断 m_pInstance是空的,于是开始实例化单例;然而在线程1创建实例时,线程2也尝试获取单例,这个时候判断m_pInstance还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法就是加锁。
- 内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用,因此会导致内存泄漏。解决办法就是使用共享指针。
饿汉式
饿汉式的特点是一开始就加载了,所以每次用到的之后直接返回。饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
#include <iostream>
using namespace std;
class Singleton
{
public:
~Singleton()
{
std::cout << "~Singleton" << std::endl;
}
static Singleton* get_instance()
{
static Singleton obj;
return &obj;
}
void use() { cout << "use singleton" << endl; }
private:
Singleton()
{
std::cout << "Singleton" << std::endl;
}
Singleton(Singleton&) = delete;//禁止调用该函数
Singleton& operator=(const Singleton&) = delete;//禁止调用该函数
};
int main()
{
Singleton* Instance1 = Singleton::get_instance();
Singleton* Instance2 = Singleton::get_instance();
return 0;
}
这是最推荐的一种单例实现方式:
- 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性)。
- 不需要使用共享指针,代码简洁。
- 注意get_instance返回的是实例的地址。
多线程的单例模式
从懒汉式和饿汉式可以看出,其区别在于看定义的是静态成员对象变量还是静态成员对象指针变量。因为如果定义了静态成员对象变量,程序在运行之初已经分配了空间,就要调用构造函数了;而线程在调用get_instance的时候,不会再调用构造函数了,因为之前已经调用过了;线程就是用的现成的,就是所谓的饿汉模式,上来先把吃得准备好了,因为饿怕了,怕后期准备会挨饿。
而定义了静态成员对象指针变量,程序运行之初也会分配空间,但是那个是指针的空间,而不是对象的空间,所以不会调用对象的构造函数,而只有调用get_instance进行new操作的时候,才会对其调用构造函数,比较懒惰,所以叫懒汉模式。懒汉式的写法会出现线程安全的问题,需要加mutex进行处理。
#include <iostream>
#include <windows.h>
using namespace std;
HANDLE g_Mutex = CreateMutex(NULL, FALSE, NULL);
class Singleton
{
public:
~Singleton()
{
std::cout << "~Singleton" << std::endl;
if (nullptr != m_pInstance)
{
delete m_pInstance;
}
}
static Singleton* get_instance()
{
WaitForSingleObject(g_Mutex, INFINITE);
if (nullptr == m_pInstance)
{
m_pInstance = new Singleton();
}
ReleaseMutex(g_Mutex);
return m_pInstance;
}
void use() { cout << "use singleton" << endl; }
private:
Singleton()
{
std::cout << "Singleton" << std::endl;
}
Singleton(Singleton&) = delete;//禁止调用该函数
Singleton& operator=(const Singleton&) = delete;//禁止调用该函数
static Singleton* m_pInstance;
};
Singleton* Singleton::m_pInstance = NULL;
DWORD WINAPI ThreadFunction1(LPVOID lpThreadParameter)
{
Singleton* Instance1 = Singleton::get_instance();
return 0;
}
DWORD WINAPI ThreadFunction2(LPVOID lpThreadParameter)
{
Singleton* Instance2 = Singleton::get_instance();
return 0;
}
int main()
{
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, 0, ThreadFunction1, 0, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadFunction2, 0, 0, NULL);
WaitForMultipleObjects(2, hThread, true, INFINITE);
return 0;
}
从代码中可以看出g_Mutex是互斥锁,避免两个线程同时创建Singleton实例。
- 上一篇: C++为什么不提倡使用单例模式?
- 下一篇: C++ 设计模式之单例模式(含代码)
猜你喜欢
- 2024-09-24 学习C++之良好的编程习惯与编程要点
- 2024-09-24 大一萌新看过来,“这样”学C++,让你不再迷茫
- 2024-09-24 学习单例模式引发的思考
- 2024-09-24 C++中相互依赖的全局变量初始化策略
- 2024-09-24 零基础学习C++
- 2024-09-24 c++的面试总结
- 2024-09-24 避免踩坑,C++常见面试题的分析与解答
- 2024-09-24 20道必须掌握的C++题,纸上谈兵惯用伎俩
- 2024-09-24 C++11魔法静态变量magic static
- 2024-09-24 C++11 中值得关注的几大变化
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)