首先,面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况讲,面向对象的成本大多可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。单例模式就可以控制这种成本。
- 为什么要使用单例模式
在软件系统设计中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑性正确、以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这应该是设计者的责任,而不是使用者的责任。
- 主要解决的问题
一个全局使用的类频繁地创建与销毁。
- 怎么实现
构造函数是私有的。
- 单例模式的优点
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存);
避免对资源的多重占用(比如写文件操作,Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。);
- 单例模式的缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
需要注意:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
- 懒汉式实现单例模式,最后一个函数是双检查锁的方法
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) //注意这里为什么要加这条if,如果你加的话,多线程情况有可能创建多个实例,考虑原因
m_instance = new Singleton();
}
return m_instance;
}
//经过研究者大量的额实验发现,这种情况很有肯能发生,因此不能使用
//我们认为m_instance = new Singleton();底层的执行顺序是分三步的
//第一步:分配内存 第二步:调用构造器 第三步:把指针赋给m_instance
//但是CPU在进行指令优化的后,可能把第二步和第三步的执行顺序调换,如果这个时候另一个线程进来在第一个
//判断处就不会是nullptr,但是实际上还没有执行构造函数,因此,这个时候执行return会出错
- 恶汉式实现单例模式
//这种方式比较常用,但容易产生垃圾对象。
//优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance(){return m_instance;};
static Singleton* m_instance;
};
Singleton* m_instance = new Singleton();