前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >设计模式-单例设计模式

设计模式-单例设计模式

作者头像
啊QQQQQ
发布于 2025-04-09 01:03:08
发布于 2025-04-09 01:03:08
11400
代码可运行
举报
文章被收录于专栏:C++C++
运行总次数:0
代码可运行

什么是单例设计模式?

单例设计模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例;

简单来说就是单例类只能实例化出一个对象;概念不难理解,我们要探讨的就是如何实现单例类,也就是如何控制一个类才能保证只有一个类呢?

为什么要使用单例模式?

资源方面

  • 避免资源重复占用:在某些场景下,创建多个相同的对象会造成资源的浪费。比如数据库连接,每次创建数据库连接都需要消耗一定的系统资源,像网络资源、内存资源等。使用单例模式,就能保证整个应用程序中只有一个数据库连接实例,避免了重复创建连接带来的资源浪费,提高了资源的利用效率。
  • 控制资源访问:对于一些共享资源,如文件系统、打印机等,单例模式可以对资源的访问进行有效的控制。以文件系统为例,如果多个对象同时对同一个文件进行写操作,可能会导致数据混乱。通过单例模式,可以确保同一时间只有一个对象对文件进行操作,保证了数据的一致性和完整性。

数据一致方面

  • 全局数据共享:单例对象可以作为全局数据的存储和访问点,在应用程序的不同模块之间共享数据。例如,在一个游戏应用中,游戏的配置信息(如游戏难度、关卡设置等)可以存储在单例对象中,各个游戏模块都可以通过单例对象来获取和修改这些配置信息,保证了数据的一致性和同步性。
  • 状态管理:在一些需要维护全局状态的应用中,单例模式可以很好地管理这些状态。比如,一个在线购物系统中的购物车,使用单例模式可以确保用户在整个购物过程中只有一个购物车实例,无论用户在哪个页面进行商品添加、删除等操作,都是对同一个购物车进行操作,避免了状态不一致的问题。

系统性能方面

  • 减少对象创建开销:创建对象需要分配内存、初始化等操作,这些操作会消耗一定的时间和系统资源。单例模式只创建一个对象,避免了多次创建对象的开销,提高了系统的性能。特别是在对象创建成本较高的情况下,如创建数据库连接池、线程池等,单例模式的优势更加明显。
  • 提高访问效率:由于单例对象的全局访问点是固定的,访问单例对象的速度比频繁创建和销毁对象要快得多。在需要频繁访问某个对象的场景下,使用单例模式可以显著提高系统的响应速度。

代码维护方面

  • 简化代码结构:单例模式将对象的创建和管理逻辑集中在一个类中,使得代码结构更加清晰和简洁。在整个应用程序中,只需要关注单例类的实现和使用,而不需要在各个模块中重复编写对象创建和管理的代码,降低了代码的复杂度,提高了代码的可维护性。
  • 便于代码修改和扩展:当需要对单例对象的功能进行修改或扩展时,只需要在单例类中进行修改,而不需要在整个应用程序中查找和修改相关代码。这样可以减少代码修改的工作量,降低引入新错误的风险。

如何设计单例类?

单例分为饿汉模式和懒汉模式

在说模式之前,我们需要先知道怎么设计才可以让一个类只能有一个实例化对象呢?

我们回顾一下之前的创建一个类的方式;

-->调用构造函数对吧,而且是public权限的构造函数;

诶?那我们如果把构造函数设置为私有的了,那么类外这不就无法创建对象了吗,确实是起到了限制对象数量的作用;

但这样我们该如何创建对象呢?

-->类外无法创建那就在类内创建对象!因为单例本就是只有一个对象,该对象会被其他线程共享,数据都是一致的,所以我们只需要维护好这一个类的方法就好了,在哪里创建不关心这个;

我们在类内创建一个对象;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class A
{
private:
	A() 
	{}
private:
	A _ins;  
public:
	void f()
	{
		cout << "创建一个单例对象" << endl; 
	}
};

但是这里会出现问题,那么就A类还没有构建完毕,是不可以使用A类型作为成员的;

那我们应该如何定义呢?--->静态成员变量;

静态成员变量是比较特殊的成员变量,他处于全局区,每一个类只能实例化出的里面的静态成员都是同一个;

需要注意在类外初始化;正是因为他是在类外实例化的,所以不会和A类同时构造,而是实例化的时间不同;不会出现上述报错;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class A
{
private:
	A() 
	{}
private:
	static A _ins;    
public:
	void f()
	{
		cout << "创建一个单例对象" << endl; 
	}
	static A GetA()
	{
		return _ins; 
	}
};

所以我们现在已经可以通过类内创建A对象。并且也是可以直接通过域名直接获得,但是为了体现封装性最好使用一个接口;当然这个接口也是要使用static,因为我们要使用域名访问该接口,然后获得单例对象;

饿汉模式

饿汉式在类加载时就立即创建单例对象,不管这个对象是否马上会被使用。就好像一个 “饥饿” 的人,迫不及待地在一开始就把东西准备好。

映射到代码上就是我先创建好一个对象,但是可能没有被使用,一等到有用户要用,立马给用户,这中间没有多余的动作;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<bits/stdc++.h>
#include<mutex>
using namespace std;
namespace hunger_instance {
	//饿汉模式
	class Instance
	{
	public:
		static Instance* GetInstance()
		{
			return &_ins;
		}
		void f()
		{
			cout << "获取到了一个单例对象..." << endl;
		}
	private:

		Instance(const Instance&) = delete;
		Instance() = default;
	private:
		static Instance _ins;
	};

	Instance Instance::_ins;
}

代码中可以很好的体现出饿汉性质,_ins先初始化好了,用户可以直接通过域名调用GetInstance()获得单例对象地址;

懒汉模式

懒汉核心特点是在需要使用单例对象时才进行创建,即延迟加载。不会像饿汉方式一样,先把对象创建好,用的时候直接拿,懒汉不会先创建好对象,而是需要时才创建;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace lazy_instance
{
	std::mutex mtx;
 	//饿汉模式
	class Instance
	{
	public:
		static Instance* GetInstance()
		{
			if (_ins == nullptr)
			{
				{
					std::lock_guard<std::mutex>lock(mtx);  
					if (_ins == nullptr)
					{
						_ins = new Instance();
					}
				}
			}
			return _ins; 
		}
		void f()
		{
			cout << "获取到了一个单例对象..." << endl;
		}
	private:

		Instance(const Instance&) = delete;
		Instance() = default;
	private:
		static Instance* _ins;
	};
	Instance* Instance::_ins = nullptr;    
}

由于不是在类外先创建好的,所以我们不能使用普通对象,以为普通对象类外一初始化就是实例化了;

所以懒汉模式的对象是指针;类外初始化为空指针;

与饿汉模式另一点不同的是GetInstance方法,这里因为会存在线程安全所以需要用到互斥锁,以及双层循环保证只new一个对象;

如何理解懒汉模式的双层判断解决线程安全?

这里我以3个线程为例,分别为1、2、3号线程,同时要获取单例对象;

假设是1号线程先拿到了锁,还没有new呢,对象这个时候还是nullptr,但是这个时候2号线程也通过了第一层判断进来了,阻塞在了锁处,因为拿不到锁;

然后1号线程创建完对象,释放锁给2号线程,2号线程进来后,需要看一看对象是不是已经创建好了,2号对象发现对象指针不为空,说明已经创建好了,所以什么也不用干释放锁;也可以拿到对象指针;

3号线程这时候来了,在第一层判断处,他就发现指针不为空,那我直接return _ins就好了;

总结:双层判断是必要的,第一个判断很简单,就是避免进入该方法的线程避免在指针不为空的情况下还new;

第二层循环配合锁是对象还没创建完时,避免进入第一层循环内部的多个线程多次new对象;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
LV.0
全球人工智能信息服务
目录
  • 什么是单例设计模式?
  • 为什么要使用单例模式?
    • 资源方面
    • 数据一致方面
    • 系统性能方面
    • 代码维护方面
  • 如何设计单例类?
    • 在说模式之前,我们需要先知道怎么设计才可以让一个类只能有一个实例化对象呢?
    • 饿汉模式
    • 懒汉模式
  • 如何理解懒汉模式的双层判断解决线程安全?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档