特殊类设计[下] --- 单例模式

文章目录

5.只能创建一个对象的类

5.1设计模式2.5 万字详解:23 种设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

为什么会产生设计模式这样的东西呢?

就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。春秋战国时期,七国之间经常打仗,发现打仗也是有套路的,孙子就总结出了《孙子兵法》

使用设计模式的目的是什么呢?

代码可重用性、代码更容易被理解、代码可靠性、代码编写工程化

cpp 复制代码
设计模式是软件工程的基石脉络,如同大厦的结构一样

5.2单例模式

一个类只能创建一个对象,即单例模式

该模式可以保证系统中(一个进程)该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。在此进程全局只有唯一一个 且 在任意地方可访问

应用场景

在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取 ,服务进程中的其他对象通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

1.饿汉模式

1.懒汉模式

6.饿汉模式

程序启动时(main函数之前)就创建一个唯一的实例对象

cpp 复制代码
class Singleton
{
public:
	static Singleton* GetPtrAtOnly()
	{
		return _ponly;
	}

	void PushData(const string& str)
	{
		_mtx.lock();

		_vec.push_back(str);

		_mtx.unlock();
	}

	void Display()
	{
		_mtx.lock();

		for (auto& e : _vec)
		{
			cout << e << endl;
		}
		cout << endl;

		_mtx.unlock();
	}

private:
	//构造函数私有化 -- 禁止类外创建对象
	Singleton()
	{
	
	}

private:
	mutex _mtx;
	vector<string> _vec;

	//_ponly是一个存在于静态区的指针变量
	//这个指针初始化指向 一个Singleton对象
	static Singleton* _ponly;
};

//在程序入口之前就完成单例对象的初始化
//类内声明 类外初始化
Singleton* Singleton::_ponly = new Singleton;

int main()
{
	//Singleton s1;
	//static Singleton s2;
	//Singleton* p = new Singleton;

	Singleton::GetPtrAtOnly()->PushData("彭于晏");
	Singleton::GetPtrAtOnly()->PushData("吴彦祖");
	Singleton::GetPtrAtOnly()->PushData("黎明");
	Singleton::GetPtrAtOnly()->PushData("郭富城");
	Singleton::GetPtrAtOnly()->Display();
	return 0;
}


多线程单例模式之饿汉模式测试

cpp 复制代码
int main()
{
	srand(time(0));

	int n = 10;
	thread t1([n]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				Singleton::GetPtrAtOnly()->PushData("线程1: " + to_string(rand()));
			}
		}
	);

	thread t2([n]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				Singleton::GetPtrAtOnly()->PushData("线程2: " + to_string(rand()));
			}
		}
	);

	t1.join();
	t2.join();

	Singleton::GetPtrAtOnly()->Display();

	return 0;
}

7.懒汉模式

7.1饿汉模式优缺点:

优点:相对懒汉模式而言简单一些

缺点:

  1. 影响进程启动速度
    饿汉模式main函数之前就要创建对象
    若单例对象初始化很慢(初始化操作很多[读取配置文件]) 对象1暂时不占用资源
    但是会影响后续程序的启动速度
  2. 多个单例类对象 实例启动顺序不确定
    两个有依赖关系的单例都是饿汉时
    若要求创建顺序:单例1--单例2
    饿汉模式无法控制顺序

7.2懒汉模式

1.线程安全问题

  1. 懒汉模式
cpp 复制代码
static Singleton* GetPtrAtOnly()
{
	if (_ponly == nullptr)
	{
		if (_ponly == nullptr)
		{
			_ponly = new Singleton;
		}
	}
	return _ponly;
}

假设两个线程 线程1的对象实例化后进行了添加数据 此时线程2执行 覆盖线程1

  1. 饿汉模式不用考虑

线程在main函数后进行 饿汉模式在main函数前就创建了对象

cpp 复制代码
    static Singleton* GetPtrAtOnly()
	{
		return _ponly;
	}
	
Singleton* Singleton::_ponly = new Singleton;

加锁保护

cpp 复制代码
	static Singleton* GetPtrAtOnly()
	{
	    _imtx.lock();
		if (_ponly == nullptr)
		{
			_ponly = new Singleton;
		}
		_imtx.unlock();

		return _ponly;
	}

每次创建对象都要 加锁解锁 有无改进办法?

cpp 复制代码
	static Singleton* GetPtrAtOnly()
	{
		if (_ponly == nullptr)
		{
			_imtx.lock();
				_ponly = new Singleton;
			_imtx.unlock();
		}
		return _ponly;
	}

此时相当于没加锁 跟没加锁造成的问题一样 以下的双检查加锁才是解决办法

cpp 复制代码
	static Singleton* GetPtrAtOnly()
	{
	    //懒汉模式 不在外部加锁 提高效率 -- 要不然每次创建对象都要加锁
		if (_ponly == nullptr)
		{
			_imtx.lock();
			
            //线程安全 t1判断为空 new对象 t2来了不为空 不再new 更正了覆盖问问题
			if (_ponly == nullptr)
			{
				_ponly = new Singleton;
			}

			_imtx.unlock();
		}

		return _ponly;
	}

2.单例对象的析构问题



8.整体代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map> 
#include <thread> 
#include <functional>
#include <assert.h>
#include<mutex>
using namespace std;


// 饿汉模式:
/*
class Singleton
{
public:
	static Singleton* GetPtrAtOnly()
	{
		return _ponly;
	}

	void PushData(const string& str)
	{
		_mtx.lock();

		_vec.push_back(str);

		_mtx.unlock();
	}

	void Display()
	{
		_mtx.lock();

		for (auto& e : _vec)
		{
			cout << e << endl;
		}
		cout << endl;

		_mtx.unlock();
	}

private:
	//构造函数私有化 -- 禁止类外创建对象
	Singleton()
	{

	}

private:
	mutex _mtx;
	vector<string> _vec;

	//_ponly是一个存在于静态区的指针变量
	//这个指针初始化指向 一个Singleton对象

	//这里可以直接static Singleton _only; 他是一个对象 程序结束时调用析构
	//而懒汉模式只能是指针因为他要判断是否空再去创建对象
	//所以懒汉模式不得不写一个对象回收实现自动析构
	static Singleton* _ponly;
};
Singleton* Singleton::_ponly = new Singleton;
*/

//懒汉模式:第一次访问实例对象时[第一次调用GetPtrAtOnly()]创建
class Singleton
{
public:
	//获取单例对象
	static Singleton* GetPtrAtOnly()
	{
		if (_ponly == nullptr)
		{
			_ptrmtx.lock();

			if (_ponly == nullptr)
			{
				_ponly = new Singleton;
			}

			_ptrmtx.unlock();
		}

		return _ponly;
	}

	// 一般全局都要使用单例对象 
	// 所以单例对象一般不需要显示释放  
	// 特殊场景 -- 显示释放

	//释放单例对象
	static void DeletePtrAtOnly()
	{
		_ptrmtx.lock();
		if (_ponly != nullptr)
		{
			delete _ponly;
			_ponly = nullptr;
		}
		_ptrmtx.unlock();
	}


	void PushData(const string& str)
	{
		_vecmtx.lock();

		_vec.push_back(str);

		_vecmtx.unlock();
	}

	void Display()
	{
		_vecmtx.lock();

		for (auto& e : _vec)
		{
			cout << e << endl;
		}
		cout << endl;

		_vecmtx.unlock();
	}

	~Singleton()
	{
		// 要求程序结束时
		// 将数据写到文件 
		// 单例对象析构时[持久化]
		// 即析构前做事情 

		// 写文件操作
		//DeletePtrAtOnly();

		//存在一种情况 写文件操作代码量太大 最后忘记调用DeletePtrAtOnly();
		//此时有没有析构单例对象 怎么办? 能不能搞得智能一点?

		//类比智能指针 再搞一个类 使得实现"自动化"

		//_gc是一个静态局部变量 他的析构发生在main函数结束后 程序结束时
		//_gc析构时 会调用他的析构函数~Garbage_Collection(); 
		//他的析构时会调用单例对象的析构函数 由此实现自动化
	}

	// 单例对象回收
	class Garbage_Collection
	{
	public:
		~Garbage_Collection()
		{
			DeletePtrAtOnly();
		}
	};
	static Garbage_Collection _gc;

private:
	Singleton()
	{
	
	}

	//有锁时 不禁用拷贝构造也行 因为锁使得vector不能push_back
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;
private:
	mutex _vecmtx;
	vector<string> _vec;

	static mutex _ptrmtx;
	static Singleton* _ponly;
};

mutex Singleton::_ptrmtx;

Singleton* Singleton::_ponly = nullptr;

Singleton::Garbage_Collection Singleton::_gc;

int main()
{
	//Singleton s(*Singleton::GetPtrAtOnly());

	srand(time(0));

	int n = 20;
	thread t1([n]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				Singleton::GetPtrAtOnly()->PushData("线程1: " + to_string(rand()));
			}
		}
	);

	thread t2([n]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				Singleton::GetPtrAtOnly()->PushData("线程2: " + to_string(rand()));
			}
		}
	);

	t1.join();
	t2.join();

	Singleton::GetPtrAtOnly()->Display();

	return 0;
}

9.C++11后可用的单例模式

C++11单例模式简单写法:将对象定义GetPtrAtOnly()函数的局部静态变量 返回对象的引用 在GetPtrAtOnly()函数首次调用时完成静态对象初始化

当某一个线程调用GetPtrAtOnly()执行初始化静态变量时,若其他线程正在执行初始化该静态变量 则先初始化上一进程

cpp 复制代码
class Singleton
{
public:
    // C++11后才可以保证初始化静态对象的线程安全问题
	static Singleton* GetPtrAtOnly()
	{
		static Singleton one; 
		return &one;
	}

	void PushData(const string& str)
	{
		_vecmtx.lock();

		_vec.push_back(str);

		_vecmtx.unlock();
	}

	void Display()
	{
		_vecmtx.lock();

		for (auto& e : _vec)
		{
			cout << e << endl;
		}
		cout << endl;

		_vecmtx.unlock();
	}

	~Singleton()
	{

	}
private:
	Singleton(){}
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

	mutex _vecmtx;
	vector<string> _vec;
};
相关推荐
沫璃染墨11 分钟前
《从零入门Linux系统篇(五十七):线程篇·十——生产者消费者模型进阶:从环形缓冲区到POSIX信号量》
linux·运维·服务器·开发语言·c++·系统架构·信号处理
wuminyu23 分钟前
LockStack在虚拟线程Mount和Unmount拷贝过程剖析
java·linux·c语言·jvm·c++
Felven28 分钟前
B. Reverse a Permutation
c语言
Logic1011 小时前
C语言/数据结构排序题解:三个数的最大乘积——排序后比较两种候选情况
c语言·数据结构·数组·排序·时间复杂度·算法题·负数处理
知识分享小能手1 小时前
C++ 学习教程,从入门到精通,string类和标准模板库 — 完整知识点详解(16)
开发语言·c++·学习
无忧.芙桃1 小时前
位图妙用:512MB 实现 40 亿整数的存在性查询
c语言·c++·面试·哈希算法
ShineWinsu1 小时前
对于Redis:Steam、Geospatial、Hyperloglog、Bitmap、Bitfield类型的解析
数据库·c++·redis·分布式·缓存·面试·zset
听风若依1 小时前
数据在内存中的存储-纸面笔记整理
c语言·笔记
朝朝辞暮i2 小时前
C++ 第 31C 课:Subscriber 到底是什么
开发语言·c++·算法
殷色玫瑰2 小时前
C++ string类详解:常用接口、字符串操作与模拟实现
java·linux·c语言·开发语言·数据结构·c++