IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++编程之单例模式 -> 正文阅读

[C++知识库]C++编程之单例模式

目录

1.线程安全

2.线程不安全的示例

2.1示例一:

2.2示例二:

3.饿汉模式与懒汉模式的区别


所谓的单例模式,指的是类在内存中有且仅有一个对象。

1.线程安全

所谓线程安全指的是:当我们的整个程序中创建了多个线程,而这多个线程有可能运行同一段代码,如果每一次多线程程序运行这段代码的结果和单线程程序运行这段代码的结果一样,其变量值和预期一样,那么这个多线程的程序就是线程安全的。

示例:

主函数创建两个线程,使两个线程调用Object类里面的静态方法,并打印静态方法返回的对象地址

#include <iostream>
#include <thread>
using namespace std;
//1、线程安全的情况
class Object
{
private:
	int value;
	static Object instance;//静态变量需要类外初始化
	static int num;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		return instance;
	}
};
Object Object::instance(10);
void funa()
{
	Object& obja = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

程序运行结果:

由此上述代码执行之后,打印出来的两个对象地址是一样的。

解释:

静态对象instance在进入主函数之前就已经在数据区构造完成,并设置其value=10,然后进入主函数之后,无论是thra线程,还是thrb线程,都只能去访问这一个静态对象instance,并不会对其做出任何改变,所以是线程安全的方式。

2.线程不安全的示例

通过对下面的示例讲解线程不安全的两个情况,并解释什么是饿汉模式,什么是懒汉模式。

2.1示例一:

在解释饿汉模式之前,先了解一下静态变量在程序运行过程中是如何初始化的,这方便对后续的示例进行理解。

程序对静态量用字面常量和变量在底层初始化是不一样的。

示例:

void funa(int x)
{
    static int a=10;
}
int main()
{
     funa(10);
}

静态常量:在进入主函数之前遇到static int a=10,就会把a这个变量放在数据区,并开辟一定大小的空间,然后赋初值10(这是线程安全的)。

示例:

void funb(int x)
{
    static int b=x;
}
int main()
{
    funb(10);
}

静态变量:进入主函数之前遇到static int b=x;在数据区先给这个变量b分配内存,由于并不知道具体赋值为多少,所以先不赋值,此时静态变量内部会有一个标志fig=0,表示没有赋值。在主函数之后调用funb(10),再去数据区赋值,此时fig标志位从0变为1,表示已经赋初值。所以有变量的时候数据区是先修改标记fig,再修改改值(这是线程不安全的,因为如果程序存在多个线程竞争去改变数据区这个变量的标志位fig)。

通过对程序处理静态变量的理解之后,在看看下面的示例:

主函数创建两个线程,这两个线程分别调用Objec类里面的静态方法,并给x传参,然后构造一个静态对象并返回此对象。

//饿汉模式
class Object
{
public:
	int value;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance(int x)//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		//直接在静态函数中构建这个静态对象并返回其本身
		static Object instance(x);
		return instance;
	}
};
void funa()
{
	Object& obja = Object::GetInstance(10);//调用静态方法返回静态对象本身
	cout << obja.value << endl;
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance(20);//调用静态方法返回静态对象本身
	cout << objb.value << endl;
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

程序运行结果:

?第一次运行:

第二次运行:

由此可见上述的示例不是线程安全的,我们本来期望的是thra线程给x传参为10,构建一个静态对象使其value=10,然后返回这个静态对象。thrb线程给x传参为20,然后构建一个静态对象使其value=20,并返回这个静态对象。但是程序运行结果是只有一个对象,并且value的值是不确定的,并不是我们预期的结果。

原因:

static Object instance(x);这一句会在进入主函数之前就已经在数据区为其分配了地址空间,进入主函数之后启动线程,两个线程同时运行Object类里面的静态方法(静态方法只有一份),就存在资源竞争的问题,争夺标记fig从0变为1的权利,哪个线程抢到机会,就会将自己的值赋值给value,那么没有抢到的那个线程再去访问时发现fig标志已被设置为1,表明已经被赋值了,所以它就只能退出这个静态方法。

最后来说说什么是饿汉模式,通过上面构建静态对象的例子可知,饿汉模式就是我们设计的类只要开始加载,就必须把单例初始化完成,从而保证多线程在访问这一段唯一资源时,单例是已经存在的(拿上面的例子来说:就是Object这个类一旦开始加载,static Object instance(x)这个对象是需要赶紧初始化的,从而保证多个线程在调用这个唯一的静态方法时,静态对象已经存在,不需要线程做其他的改变)。

2.2示例二:

示例:

主函数启动两个线程,两个线程分别调用Object类里面的静态方法,在静态指针pobj为空的时候,从堆区构建一个Object的对象,然后返回pobj这个静态的对性指针,此指针会指向堆区构建的Object对象,然后两个线程分别打印这个指针所指对象的地址。

class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

程序运行结果:

第一次运行:

第二次运行:

通过程序运行结果可见两个线程返回的对象地址并不是一样的,但是我们期望的线程安全情况下多线程执行同一段程序,其返回的对象应该只有一个,地址是唯一的。出现现在这种情况的原因是:

两个线程都进入静态方法GetInstance()里面,都发现pobj指针为空,于是都进入If语句内,然后我设置了一个睡眠时间,然后一个执行pobj = new Object(10);创建了一个对象给pobj,并返回打印这个对象的地址,随后另外一个线程又执行pobj = new Object(10);;又创建了一个对象重新给pobj赋值,然后打印对象的地址。

所以通过上述例子可见:所谓的懒汉模式就是指在我们设计的类开始加载的时候并不需要赶紧初始化单例,而是等到第一次被使用时才初始化(所以才说它是懒汉)。对于懒汉模式如果想要保证线程安全,只创建一个对象,可以对创建对象的那部分代码加锁。

修改后代码如下:
?

#include <mutex>
std::mutex mtx;
class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		std::lock_guard<std::mutex>lock(mtx);
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

3.饿汉模式与懒汉模式的区别

(1)饿汉模式:

在类加载的时候就已经创建好对象,程序在调用的时候会直接获取先前已经构建好的实例对象。

这种方式由于构建好的对象是静态的,会一直占用数据区的空间,但是调用的时候节省了构建对象的时间。

(2)懒汉模式:

在访问静态方法的时候才去构建对象。

这种方式是在需要使用的时候才构建,在一定程度上节省了空间,避免对象一直在内存浪费空间,但是用的时候在构建,会消耗一定的时间,使得程序的速度变慢。

这种模式不安全,需要加锁进行控制。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 15:50:34  更:2022-03-03 15:56:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:48:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码