设计模式-单例模式(Singleton)

发布时间:2023年12月20日

前言

? ? ? ?在某些软件系统中,有一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。单例模式保证一个类仅有一个实例,并提供一个该实例的全局访问点。

方法

? ? ? ?绕过构造器,提供一种机制来保证一个类只有一个实例。这是类设计者的责任,不是使用者的责任。

实现

class Singleton
{
private:
	Singleton();
	Singleton(const Singleton& singletion);
public:
	static Singleton* getInstance();
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr;

需要将默认构造器及拷贝构造器定义为private、定义静态的变量。

版本1

Singleton* Singleton::getInstance()
{
	if (m_instance == nullptr)
	{
		m_instance = new Singleton();
	}
	return m_instance;
}

该版本是线程非安全的,其原因显而易见。

版本2

Singleton* Singleton::getInstance()
{
	Locker lock;
	if (m_instance == nullptr)
	{
		m_instance = new Singleton();
	}
	return m_instance;
}

? ? ? ?该版本可以达到线程安全的目的,但是存在效率上的问题,在高并发的场景下,锁的开销是非常大的。加锁是针对多线程写的操作的,加入单例对象已被创建,后续均为读取操作,但却每次都在加锁,显然是对资源白白的消耗。

版本3

Singleton* Singleton::getInstance()
{
	if (m_instance == nullptr) //line 1
	{
		Locker lock;           //line 2
		if (m_instance == nullptr)
		{
			m_instance = new Singleton();
		}
	}

	return m_instance;
}

? ? ? ? 该版本为双检查锁,首先该版本实现了对写加锁,对读不加锁。第一个锁前判断是为了避免代价过高的问题,第二个锁后检查是为了避免进入line1未到line2的情况,假如没有这个判断,试想在AB线程最终都会执行m_instance=new Singleton()。这显然也是会有问题的。这个版本存在问题,不能用,见下文讲解。

版本4

m_instance=new Singleton()的顺序:

1.分配内存;

2.调用构造器

3.将地址赋值给m_instance; 此时m_insance可以正常使用。

编译器 reorder:

1.分配内存;

2.将地址赋值给m_instance; 这一步执行完之后m_instance不是nullptr,但对象状态是不可用的。

3.调用构造器。

// 编译器厂商也做了不少工作。例如volatile。单是不跨平台

C++11的跨平台版本:

#include <iostream>
#include <mutex>
#include <atomic>

class Singleton
{
private:
	Singleton()
	{
	}
	Singleton(const Singleton& singletion)
	{
	}
public:
	static Singleton* getInstance();
	static std::atomic<Singleton*> m_instance;
	static std::mutex m_mutex;
};

std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance()
{
	Singleton* tmp = m_instance.load(std::memory_order_relaxed);
	std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence

	if (tmp == nullptr)
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		tmp = m_instance.load(std::memory_order_relaxed);
		if (tmp == nullptr)
		{
			tmp = new Singleton;
			std::atomic_thread_fence(std::memory_order_release);//释放内存fence
			m_instance.store(tmp, std::memory_order_relaxed);

			std::cout << "m_instance created!" << std::endl;
		}
	}

	return m_instance;

}

总结

? ? ? ? 版本1在单线程中无问题,可以用。版本2也可以用,但在高并发场景存在性能问题,需要考虑。版本3坚决不能用,许多学者对编译器?reorder的问题进行了统计,出现redorder的概率挺大的。版本4为CPP11之后跨平台的版本,既考虑了效率,又杜绝了reorder问题,可以放心使用。

文章来源:https://blog.csdn.net/qianlixiaomage/article/details/135038929
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。