C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理)

饿汉单例模式

程序还没有主动获取实例对象,该对象就产生了,也就是程序刚开始运行,这个对象就已经初始化了。

cpp 复制代码
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		return &singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton singleton;
};
Singleton Singleton::singleton;//类的静态成员变量要在类外定义
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

显然饿汉模式是线程安全的,因为单例对象的初始化发生在.bss段,和栈无关,而线程的启动依赖于函数,函数需要开辟栈内存,所以是线程安全的。但是饿汉模式也有缺点,如果这个单例类的构造函数过于复杂,包含了线程和数据库等等一系列的初始化过程,需要进行大量操作,就会导致程序启动变慢。

运行结果如下: 三个对象的地址是一样的,说明是同一个对象,并且最后也只是析构了一次。

懒汉模式

实例对象直到程序中有模块获取它时,才会初始化这个对象。

cpp 复制代码
#include<iostream>
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		if (singleton == nullptr)
		{
			singleton = new Singleton();
		}
		return singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

运行结果。

上面这种写法显然是线程不安全的,因为要构造一个单例,构造函数里面可能需要进行大量的操作。这段代码就会产生竞态条件,我们需要通过线程间的互斥操作来解决。

cpp 复制代码
#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		std::lock_guard<std::mutex>loc(mtx);
		if (singleton == nullptr)
		{
			singleton = new Singleton();
		}
		return singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

这种写法虽然可以解决问题,但是加锁的位置,对程序的性能损耗较大,每次要先拿到锁才去判断是否为nullptr,如果不是,这把锁就白拿了,换一下加锁的位置。

cpp 复制代码
#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		if (singleton == nullptr)
		{
			std::lock_guard<std::mutex>loc(mtx);
			singleton = new Singleton();
		}
		return singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

这次加锁位置明显可以减少程序的性能损耗,但是会出现一个问题,假如开始单例是nullptr,一个线程通过if语句,并且拿到了锁,它只是开辟了内存,并且构造了单例对象,但是构造过程没有执行完全,还没有给这个单例对象赋值, 这时候这个单例还是nullptr,另一个线程这时候也可以通过if语句了,因为单例是nullptr,但是它不能构造单例,因为没有拿到锁,这时候第一个线程给单例赋值完成后,释放了锁,第二个线程拿到锁,就又构造了一次单例。

要解决这个问题也简单,那就是双重if语句判断。

cpp 复制代码
#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		if (singleton == nullptr)
		{
			std::lock_guard<std::mutex>loc(mtx);
			if (singleton == nullptr)
			{
				singleton = new Singleton();
			}
		}
		return singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

运行结果还是一样的。

如果我们要简化上面的写法呢?我们可以使用到函数静态局部变量的初始化机制,函数静态局部变量在初始化的时候,底层的汇编指令会自动添加上线程互斥的指令,就可以省去我们加锁的步骤了。而且只有当程序主动调用get_instance函数的时候,单例才会被初始化,也省去了我们的nullptr双重判断了。

cpp 复制代码
#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:
	~Singleton()
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* get_instance()
	{
		static Singleton singleton;
		return &singleton;
	}
private:
	Singleton() {};
	Singleton(const Singleton& othersingle) = delete;
	Singleton& operator=(const Singleton& othersingle) = delete;
	static Singleton* singleton;
};
int main()
{
	Singleton* s1 = Singleton::get_instance();
	Singleton* s2 = Singleton::get_instance();
	Singleton* s3 = Singleton::get_instance();
	std::cout << "s1:" << s1 << std::endl;
	std::cout << "s2:" << s2 << std::endl;
	std::cout << "s3:" << s3 << std::endl;
}

运行效果一样。

相关推荐
小灰灰搞电子4 小时前
完全驾驭 Qt 与数据库:C++ ORM 框架 QxOrm 原理与实践指南
数据库·c++·qt
QT界面美化性能优化4 小时前
QT+AI:使用AI技术为QT应用程序赋能
c++·人工智能·qt·opencv·qt教程·qt6.3
是隼人5 小时前
buuctf-pwn xdctf2015_pwn200题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
学习星球6 小时前
单调栈——从“找下一个更大的“到柱状图中的最大矩形
数据库·c++·算法·leetcode·xcode
小灰灰搞电子7 小时前
C++ 引用折叠详解
c++
workflower8 小时前
人形机器人灵巧手产业链
人工智能·设计模式·机器人·云计算·无人机
布莱克6058 小时前
数组详解:定义、作用、应用场景及与链表的区别(C/C++ 代码讲解)
c语言·开发语言·c++·数组
hongmai6668888 小时前
MP2348GTL-Z:这颗24V/4A同步降压芯片,把性能和体积平衡得恰到好处
c语言·开发语言·arm开发·单片机·5g
多弗朗皮卡丘8 小时前
C语言梦开始的地方19:文件操作
c语言·开发语言
牛油果子哥q9 小时前
C++内存池与对象池精讲:内存碎片、自定义分配器、对象池实现、STL allocator原理、工程落地与性能对比
java·开发语言·c++