设计模式-单例模式(Singleton)
前言
? ? ? ?在某些软件系统中,有一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。单例模式保证一个类仅有一个实例,并提供一个该实例的全局访问点。
方法
? ? ? ?绕过构造器,提供一种机制来保证一个类只有一个实例。这是类设计者的责任,不是使用者的责任。
实现
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问题,可以放心使用。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!