链接: link
https://www.jianshu.com/p/edab2673ee6c
单例模式
定义:
- 保证一个类仅有一个实例,并提供一个该实例的全局访问点
- 创建一个对象,确保只有一个对象被创建
全局变量和单例的区别
全局变量是对一个对象的静态引用,全局变量确实可以提供单例模式实现的全局访问功能,但是它并不能保证应用程序只有一个实例,全局变量并不能实现继承
应用场景:
- Windows的Task Manager(任务管理器)就是很典型的单例模式,你不能同时打开两个任务管理器。Windows的回收站也是同理。
- 应用程序的日志应用,一般都可以用单例模式实现,只能有一个实例去操作文件。
- 读取配置文件,读取的配置项是公有的,一个地方读取了所有地方都能用,没有必要所有的地方都能读取一遍配置。
- 数据库连接池,多线程的线程池
懒汉式:[线程不安全版本]
class Singleton{
public:
static Singleton* getInstance(){
// 先检查对象是否存在
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
//Singleton(const Singleton& other);
static Singleton* instance; //静态成员变量
};
//静态成员需要先初始化
Singleton* Singleton::instance=nullptr;
饿汉式:(可以不看)
class Singleton{
public:
static Singleton* getInstance(){
instance = new Singleton();
return instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
//Singleton(const Singleton& other);
static Singleton* instance; //静态成员变量
};
实现二[线程安全,锁的代价过高]
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
Lock lock; //伪代码 加锁
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
分析:这种写法不会出现上面两个线程都执行new的情况,当线程A在执行instance = new Singleton()的时候,线程B如果调用了getInstance(),一定会被阻塞在加锁处,等待线程A执行结束后释放这个锁。从而是线程安全的。但这种写法的性能不高,因为每次调用getInstance()都会加锁释放锁,而这个步骤只有在第一次newSingleton()才是有必要的,只要instance被创建出来了,不管多少线程同时访问,使用if (instance ==nullptr)进行判断都是足够的(只是读操作,不需要加锁),没有线程安全问题,加了锁之后反而存在性能问题
实现三[双检查锁,由于内存读写reoder导致不安全]
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
//先判断是不是初始化了,如果初始化过,就再也不会使用锁了
if(instance==nullptr){
Lock lock; //伪代码
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
最安全的实现方式
局部静态变量不仅只会初始化一次,而且还是线程安全的。
class Singleton{
public:
// 注意返回的是引用。
static Singleton& getInstance(){
static Singleton instance; //局部静态变量
return instance;
}
private:
Singleton(); //私有构造函数,不容许使用者本身生成对象
Singleton(const Singleton& other);
};