单件模式属于 对象性能 模式
面向对象很好的解决了抽象问题,但是必不可免的要付出一定的代价.对于通常的情况来讲,面向对象的成本可以忽略不计.但是在某些情况下,面向对象带来的成本必须谨慎处理.
典型模式:
- Singleton
- Flyweight
动机
在软件系统中,经常有一些特殊的类,必须保证他们只存在一个实例,才能保证逻辑的正确性,以及良好的效率.
保证只存在一个类的实例,应该是类设计者的责任,而不是类使用者的责任.
实例
首先应该将类的构造函数和拷贝构造函数设置成私有的,利用静态成员函数来访问类的唯一实例.
class Singleton {
public:
static Singleton* GetInstance();
static Singleton* instance;
private:
Singleton();
Singleton(const Singleton& other);
};
Singleton* Singleton::instance = nullptr;
静态函数GetInstance
成员函数的创建还是很有学问的.
首先看第一种实现:
//线程非安全版本
Singleton* Singleton::GetInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
这个方式在单线程的条件是可以的,但是在多线程的情况下会出问题,假如两个线程同时进入了 if(instance == nullptr)
这个时候两个线程都会执行instance = new Singleton()
产生了两个对象.
第二个版本
//线程安全版本,但是锁的代价过高
Singleton* Singleton::GetInstance() {
Lock lock;
if (instance==nullptr) {
instance = new Singleton();
}
return instance;
}
该版本线程是安全的,可以保证,只会new
一次Singleton
.但是某一个线程执行return instance
的操作,也需要等待其他线程return
才能进行,而在变量的读操作是不需要加锁的,变量的写操作才需要加锁.在高并发的环境下,例如10w人同时对一个网页进行访问,这个加锁的代价是很高的.
第三种实现
//双检查锁,但由于内存读写reorder 不安全
Singleton* Singleton::GetInstance() {
if (instance == nullptr) {
Lock lock;
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
这种实现,看起来很完美,但是有漏洞.对于instance = new Singleton();
指令,我们理想的执行顺序是,1)分配内存,2)执行Singleton
的构造函数,3)将分配存储空间的地址赋值给instance
.但是编译器可能出于优化的考虑,真实的执行顺序为1)分配内存,2)将分配存储空间的地址赋值给instance
,3)执行Singleton
的构造函数.这种情况下,可能出现,instance
不为空指针,但是还没有初始化完成,其它线程看到这个指针不为空,就拿这个指针去用了,导致不安全的情况.
解决这个问题,其实还是比较麻烦的
//c++ 11 版本之后的跨平台实现
std::atomic<Singleton* > Singleton::instance;
std::mutex Singleton::mutex;
Singleton* GetInstance() {
Singleton* tem = instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tem == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
tmp = instance.load(std::memory_order_relaxed);
if (tem == nullptr) {
tem = new Singleton();
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
instance.store(tem,std::memory_order_relaxed);
}
}
return tem;
}
单例模式的定义,保证一个类有且仅有一个实例,并提供一个该实例的全局访问点.