前言
在某些软件系统中,有一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。单例模式保证一个类仅有一个实例,并提供一个该实例的全局访问点。
方法
绕过构造器,提供一种机制来保证一个类只有一个实例。这是类设计者的责任,不是使用者的责任。
实现
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问题,可以放心使用。