首页 > 其他分享 >设计模式-单例模式

设计模式-单例模式

时间:2024-12-29 11:27:28浏览次数:7  
标签:getInstance CSingleton 模式 single 线程 单例 static 设计模式

设计模式概念

        设计模式简单来说就是在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,与我们自己摸索出来的问题解决之道相比较,有以下优点可取:
1、代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好
2、当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的“开-闭原则”,即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活
3、合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的根本原则“高内聚,低耦合”。

单例模式简介

        单例模式指的是,无论怎么获取,永远只能得到该类类型的唯一一个实例对象,那么设计一个单例就必须要满足下面三个条件:
1.构造函数私有化,这样用户就不能任意定义该类型的对象了
2.定义该类型唯一的对象
3.通过一个static静态成员方法返回唯一的对象实例

饿汉单例模式

下面代码演示一个饿汉式单例模式:

#include<iostream>
using namespace std;
class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		return &single;
	}
private:
	static CSingleton single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);//防止外部使用拷贝构造产生新的对象,如下面CSingleton s = *p1;
};
CSingleton CSingleton::single;

int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	return 0;
}

        可以看到,三次获取的CSingleton对象都是同一个对象实例,这是一个饿汉式单例模式。
饿汉式单例模式,顾名思义,就是程序启动时就实例化了该对象,并没有推迟到第一次使用该对象时再进行实例化;如果运行过程中没有使用到,该实例对象就被浪费掉了。


懒汉单例模式

下面继续演示一个懒汉式单例模式:

#include<iostream>
using namespace std;
class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		if (nullptr == single)
		{
			single = new CSingleton();
		}
		return single;
	}
private:
	static CSingleton* single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);
};
CSingleton* CSingleton::single = nullptr;

int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	return 0;
}

        符合单例模式的要求,三次获取的都是同一个对象,而且程序启动时,只对single指针初始化了空值,等第一次调用getInstance函数时,由于single指针为nullptr,才进行对象的实例化,所以是一个懒汉式单例模式。

        所以,懒汉式单例模式,顾名思义,就是对象的实例化,延迟到第一次使用它的时候。

        我们看,上面new出来的对象,没见过delete,这样不好吧,当然了,有new没有delete,不配对啊!还有人说,管它呢,当前进程结束的时候,系统反正会回收分配给它的所有资源,包括未回收的内存,但是作为C++开发者,资源的分配和回收,我们必须要考虑清楚,不能糊涂,那么下面的修改感觉如何:

int main()
{
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	delete p1;//这里delete之前new过的对象,析构对象并且释放堆上的内存
	return 0;
}

        这种方式怎么看,怎么不舒服,首先资源的释放如果交给用户来操作,难免会忘记写delete,又或者多次delete,成释放野指针了,所以上面释放单例对象资源的方式不够好,我们利用static静态对象在程序结束时自动析构这么一个特征,给出如下释放资源的代码,肯定比上面的方式要好,代码如下:

#include<iostream>
using namespace std;
class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		if (nullptr == single)
		{
			single = new CSingleton();
		}
		return single;
	}
private:
	static CSingleton* single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);

	//定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	//通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
	static CRelease release;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;

int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	return 0;
}

        对象实例已经正常析构,内存释放。


线程安全的单例模式

        在开发服务器程序的时候,经常会用到多线程,多线程要考虑代码的线程安全特性,不能让代码在多线程环境下出现竞态条件,否则就要进行线程互斥操作,我们来考虑一下上面两种单例模式,如果用在多线程环境当中,是否是线程安全的单例模式。

1. 饿汉单例模式的线程安全特性


饿汉单例模式中,单例对象定义成了一个static静态对象,它是在程序启动时,main函数运行之前就初始化好的,因此不存在线程安全问题,可以放心的在多线程环境中使用。

2. 懒汉单例模式的线程安全特性


懒汉单例模式,获取单例对象的方法如下:

static CSingleton* getInstance()
{
	if (nullptr == single)
	{
		single = new CSingleton();
	}
	return single;
}

        很明显,这个getInstance是个不可重入函数,也就它在多线程环境中执行,会出现竞态条件问题,首先搞清楚这句代码,single = new CSingleton()它会做三件事情,开辟内存,调用构造函数,给single指针赋值,那么在多线程环境下,就有可能出现如下问题:

1.    线程A先调用getInstance函数,由于single为nullptr,进入if语句

2.  new操作先开辟内存,此时A线程的CPU时间片到了,切换到B线程

3. B线程由于single为nullptr,也进入if语句了,开始new操作

        很明显,上面两个线程都进入了if语句,都试图new一个新的对象,不符合单例模式的设计,那该如何处理呢?对了,应该为getInstance函数内部加锁,在线程间进行互斥操作。此处介绍Linux系统下,pthread库中提供的线程互斥操作方法-mutex互斥锁,代码如下:

#include <iostream>
#include <pthread.h>
using namespace std;

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		//获取互斥锁
		pthread_mutex_lock(&mutex);
		if (nullptr == single)
		{
			single = new CSingleton();
		}
		//释放互斥锁
		pthread_mutex_unlock(&mutex);
		return single;
	}
private:
	static CSingleton* single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton()
	{
		pthread_mutex_destroy(&mutex); // 释放锁
		cout << "~CSingleton()" << endl;
	}
	CSingleton(const CSingleton&);

	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	static CRelease release;

	//定义线程间的互斥锁
	static pthread_mutex_t mutex;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;
//互斥锁的初始化
pthread_mutex_t CSingleton::mutex = PTHREAD_MUTEX_INITIALIZER;

int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	return 0;
}

        既满足了效率,又满足线程安全,一举两得!既然是在学习C++,不如把互斥锁封装成一个类,使用起来更加OOP,代码如下所示:

#include <iostream>
#include <pthread.h>
using namespace std;

//对互斥锁操作的封装
class CMutex
{
public:
	CMutex() { pthread_mutex_init(&mutex, NULL); }  // 初始化锁
	~CMutex() { pthread_mutex_destroy(&mutex); }  // 销毁锁 
	void lock() { pthread_mutex_lock(&mutex); }  // 获取锁
	void unlock() { pthread_mutex_unlock(&mutex); }  // 释放锁
private:
	pthread_mutex_t mutex;
};

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		if (nullptr == single)
		{
			//获取互斥锁
			mutex.lock();
			/*
			这里需要再添加一个if判断,否则当两个
			线程都进入这里,又会多次new对象,不符合
			单例模式的涉及
			*/
			if (nullptr == single)
			{
				single = new CSingleton();
			}
			//释放互斥锁
			mutex.unlock();
		}

		return single;
	}
private:
	static CSingleton* single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);

	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	static CRelease release;

	//线程间的静态互斥锁
	static CMutex mutex;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;
//定义互斥锁静态对象
CMutex CSingleton::mutex;

int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	return 0;
}

问题思考

请思考下面这个懒汉单例模式是否是线程安全的,代码如下:

#include <iostream>
using namespace std;

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		static CSingleton single;//懒汉式单例模式,定义唯一的对象实例
		return &single;
	}
private:
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);
};
int main()
{
	CSingleton* p1 = CSingleton::getInstance();
	CSingleton* p2 = CSingleton::getInstance();
	CSingleton* p3 = CSingleton::getInstance();
	return 0;
}

        上面的单例模式在多线程环境中使用时,会不会出现这种情况,线程A第一次调用getInstance函数的时候,single对象第一次初始化,此时线程B也调用getInstance函数,会不会也进行single对象的初始化呢,因为此时线程A并没有初始化完single?

        在Linux环境中,通过g++编译上面的代码,命令如下:
g++ -o main main.cpp -g
        生成可执行文件main,用gdb进行调试,到getInstance函数,并打印该函数的汇编指令,如下:

        可以看到,对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式! 

标签:getInstance,CSingleton,模式,single,线程,单例,static,设计模式
From: https://blog.csdn.net/2301_78353179/article/details/144795039

相关文章

  • 如何使用CSS设置动画的填充模式?
    在CSS中,你可以使用animation-fill-mode属性来设置动画的填充模式。这个属性决定了动画在执行之前和之后如何应用样式。animation-fill-mode属性可以有以下几个值:none:默认值,动画在执行之前和之后不会应用任何样式到目标元素。forwards:动画结束后,元素将保持动画结束时的样式。b......
  • 【设计模式与体系结构】创建型模式-建造者模式
    简介建造者模式指的是将一个复杂对象的创建与表示分离,使得同样的创建过程可以创建不同的表示,分离了部件的构造(由Builder负责)和装配(由Director负责)。从而可以构造出复杂的对象,这个模式适用于某个对象的构建过程复杂的情况。由于实现了构建和装配的解耦,不同的构建器,相同的装配,......
  • Java设计模式 —— 【结构型模式】享元模式(Flyweight Pattern) 详解
    文章目录概述结构案例实现优缺点及使用场景概述享元模式也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象;常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们......
  • 行为型设计模式2
    第四篇章行为型设计模式第五章状态模式一、简介概述从今天起,我们开始学习状态模式。在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点像我们之前讲到的组合模式。一般不用,特定场景下使用。状态设计模式是一......
  • 行为型设计模式1
    第四篇章行为型设计模式在设计模式的世界里,23种经典设计模式通常被分为三大类:创建型、结构型和行为型。我们已经探讨了创建型和结构型设计模式,现在我们将开始学习行为型设计模式。正如创建型设计模式关注于对象创建的问题,结构型设计模式关注于类或对象的组合和组装问题,行......
  • 【电商系统】使用多种设计模式重构电商系统登录业务场景
    一、介绍在做业务功能开发的时候,每个程序员接触过最多的应该就是登录功能了,而一个登录功能有很多种登录交互的体现,当我们开始写登录代码的时候,前期能满足登录就行了,渐渐的系统中加入了不同业务需求的代码,以及每个登录需要特殊处理的参数,基本上我们要开发不同的接口和判断逻辑,对于......
  • JAVA设计模式总结之23种设计模式
    JAVA设计模式总结之23种设计模式|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|-------------|--------......
  • 原生 js 策略模式的理解
    原生js策略模式的理解策略模式(StrategyPattern)是一种行为设计模式,它允许在运行时根据不同的情况选择不同的算法或行为。该模式将算法封装成独立的策略对象,使得这些策略对象可以互相替换,从而使得算法的变化独立于使用算法的客户端。–来自查特著迪皮需求想要实现......
  • 智能问答模型升级,通义灵码新增图片多模态问答模式等新能力,项目秒上手
    通义灵码智能问答模型升级Qwen2.5Coder、输入交互升级并丰富上下文支持,全新支持多模态图片问答模式等。1.智能问答模型升级到最新Qwen2.5Coder智能问答升级到最新Qwen2.5Coder模型,编程性能和效率均实现大幅提升,其旗舰代码模型在十余项基准评测中均取得开源最佳成绩,成为......
  • 智能问答模型升级,通义灵码新增图片多模态问答模式等新能力,项目秒上手
    通义灵码智能问答模型升级Qwen2.5Coder、输入交互升级并丰富上下文支持,全新支持多模态图片问答模式等。1.智能问答模型升级到最新Qwen2.5Coder智能问答升级到最新Qwen2.5Coder模型,编程性能和效率均实现大幅提升,其旗舰代码模型在十余项基准评测中均取得开源最佳成绩,成为......