Singleton单件模式是一种“对象性能”模式
- 面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来说,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理
动机
- 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率
- 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
- 这应该是类设计者的责任,而不是使用者的责任
定义
- 保证一个类仅有一个实例,并提供一个该实例的全局访问点
结构
![](https://i-blog.csdnimg.cn/blog_migrate/c268640329022a4d4b778dc6da4c3157.png)
代码对比
Singleton.cpp
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价过高(都是读取操作的话会一直等待)
Singleton* Singleton::getInstance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton(); //先分配内存,再调用构造器,把指针的返回值(内存地址)给m_instance。这三步有可能reorder(由123变成132)
}
}
return m_instance;
}
//C++ 11版本之后的跨平台实现 (volatile)
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);
}
}
return tmp;
}
对比
- Singleton单件模式中单件类要把构造函数和拷贝构造函数设置为
private
- 线程安全版本下锁的代价过高(都是读取操作的话会一直等待),尤其在高并发状态下
- 50行双检查锁下可能由于内存读写reorder安全
- 先分配内存,再调用构造器,把指针的返回值(内存地址)给m_instance。这三步有可能reorder(由123变成132)
- 高级语言使用
volatile
关键字保证编译器在编译时不会reorder
要点总结
- Singleton模式中的实例构造器可以设置为
protected
以允许子类派生 - Singleton模式一般不要支持拷贝构造函数和
Clone
接口,因为这样有可能导致多个对象实例,与Singleton模式的初衷违背 - 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现