设计模式之单例模式
保证一个类仅有一个实例,并提供一个访问他的全局访问点。通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且可以提供一个访问该实例的接口。其UML图如下:
其示例代码如下:
1 // SingeltonModel.h文件
2 #pragma once
3 // 线程不安全类
4 class Singelton
5 {
6 private:
7 Singelton() {}
8 static Singelton * m_singel;
9 public:
10 static Singelton * getInstance()
11 {
12 if (nullptr == m_singel)
13 {
14 std::cout << "创建实例" << std::endl;
15 m_singel = new Singelton();
16 }
17 return m_singel;
18 }
19 };
20 Singelton * Singelton::m_singel = nullptr;
测试代码如下:
1 #include <iostream>
2 #include "SingeltonModel.h"
3
4 int main()
5 {
6 using namespace std;
7 // 单例模式
8 Singelton * p1 = Singelton::getInstance();
9 Singelton * p2 = Singelton::getInstance();
10 if (p1 == p2)
11 std::cout << "一致" << std::endl;
12 else
13 std::cout << "不一致" << std::endl;
14
15 getchar();
16 return 0;
17 }
测试结果如下图:
以上代码是有安全隐患的,如果该类在多线程中使用的话。当多个线程都第一次访问该类的是实例的时候有可能会出现创建两个实例,这样就出现内存泄漏了。并且两次的到的实例不是同一个,如下代码:
1 #include <iostream>
2 #include <thread>
3 #include <Windows.h>
4 #include "SingeltonModel.h"
5
6 void func1()
7 {
8 Sleep(1);
9 Singelton * p1 = Singelton::getInstance();
10 std::cout << "func1 thread:" << p1 << std::endl;
11 }
12
13 void func2()
14 {
15 Sleep(1);
16 Singelton * p2 = Singelton::getInstance();
17 std::cout << "func2 thread:" << p2 << std::endl;
18 }
19
20 int main()
21 {
22 using namespace std;
23 // 单例模式
24 std::thread thread1(func1), thread2(func2);
25 thread1.detach();
26 thread2.detach();
27 std::cout << "主线程" << std::endl;
28
29 getchar();
30 return 0;
31 }
输出结果如下图:
由上图可看出,创建了两次实例,两个线程得到的实例并不是同一个。这是因为两个线程同时运行,当调用getInstance()时,实例并没有创建,于是两个线程都进入了创建实例的代码块,于是就创建了两个实例。而第一个被创建的实例被顶替后也没有被释放,这就是内存泄漏,还有先调用到创建实例的线程还得到了错误的实例,这样会造成逻辑错误的。
解决办法有两种,一种办法是在所有要访问该类实例的代码执行前该类先创建实例,另一种是在该类的访问实例的代码中加入线程同步的内容。实例代码如下:
方法一:
1 #include <iostream>
2 #include <thread>
3 #include <Windows.h>
4 #include "SingeltonModel.h"
5
6 void func1()
7 {
8 Sleep(1);
9 Singelton * p1 = Singelton::getInstance();
10 std::cout << "func1 thread:" << p1 << std::endl;
11 }
12
13 void func2()
14 {
15 Sleep(1);
16 Singelton * p2 = Singelton::getInstance();
17 std::cout << "func2 thread:" << p2 << std::endl;
18 }
19
20 int main()
21 {
22 using namespace std;
23 // 单例模式
24 // 在开启线程前,先获取一次实例。
25 Singelton::getInstance();
26 std::thread thread1(func1), thread2(func2);
27 thread1.detach();
28 thread2.detach();
29 std::cout << "主线程" << std::endl;
30
31 getchar();
32 return 0;
33 }
输出结果如下图:
方法二:
1 // SingeltonModel.h文件
2 #include <mutex>
3 #pragma once
4
5 // 线程安全类
6 class SingeltonThread
7 {
8 private:
9 SingeltonThread() {}
10 static SingeltonThread * m_instance;
11 static std::mutex m_mutex;
12 public:
13 static SingeltonThread * getInstance()
14 {
15 if (nullptr == m_instance)
16 {
17 m_mutex.lock();
18 if (nullptr == m_instance)
19 m_instance = new SingeltonThread();
20 m_mutex.unlock();
21 }
22 return m_instance;
23 }
24 };
25 SingeltonThread * SingeltonThread::m_instance = nullptr;
26 std::mutex SingeltonThread::m_mutex;
测试代码如下:
1 #include <iostream>
2 #include <thread>
3 #include <Windows.h>
4 #include "SingeltonModel.h"
5
6 void func1()
7 {
8 Sleep(1);
9 SingeltonThread * p1 = SingeltonThread::getInstance();
10 std::cout << "func1 thread:" << p1 << std::endl;
11 }
12
13 void func2()
14 {
15 Sleep(1);
16 SingeltonThread * p2 = SingeltonThread::getInstance();
17 std::cout << "func2 thread:" << p2 << std::endl;
18 }
19
20 int main()
21 {
22 using namespace std;
23 // 单例模式
24 // 线程安全
25 std::thread thread1(func1), thread2(func2);
26 thread1.detach();
27 thread2.detach();
28 std::cout << "主线程" << std::endl;
29
30 getchar()
31 return 0;
32 }
输出结果如下图:
这两种方法都各有优势,方法一实现简单,但在复杂的系统中不太安全。方法二实现稍微复杂,而且每次访问实例都要lock(mutex),消耗更大。但是方法二在任何情况下都很安全。总之方法一适合在系统不是太复杂的情况下使用,方法二在系统比较复杂的情况下使用。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
2020-07-07 Qt 编译时报错“退出,退出代码2”的原因