单例模式

一、单例模式

保证一个类仅有一个实例,并且提供一个该类的全局访问点

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

1.1饿汉模式

在程序启动的时候,就已经把对象创建好了,它的实现比较简单,可以随时用随时拿,但是会影响程序的启动时间.

实现原理

  • 构造函数私有化

  • 在类中包含一个静态的类对象

  • 提供一个静态的成员方法,返回静态对象的地址.

template <typename T>
class Singleton {
public:
	// 局部变量只初始化一次,返回对象
	static T* GetInstance() {
		return &data;
	}
privatestatic T data;
};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例

参考文章:https://blog.csdn.net/lvyibin890/article/details/81943637

1.2懒汉方式

在程序需要的时候才创建,可以起到延迟加载,提高系统的启动速度

提供一个方法类

class Singleton{
public:
	~Singleton(){}
	static Singleton* getInstance();
	static Singleton* m_instance;
private:
	Singleton();
	Singleton(const Singleton& other);
};

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 不安全
new 方法的构造和赋值顺序可能颠倒
m_instance = new Singleton(); 这行代码按照我们正常的执行顺序是

  1. operator new 函数在内存划分一块地址
  2. 调用构造器,进行内存的初始化
  3. 然后把内存的首地址返回给 m_instance

但是 CPU 可能会把第2步和第3步调换顺序,还没有执行构造器就把地址返回给m_instance.这样一来,如果另一个线程也进入到函数内部,会返回m_instance,这个指针是不可以用的

Singleton* Singleton::getInstance(){
	if(m_instance == nullptr){
		// 如果没有第二个判断,这行代码可能会有多个线程执行到
		Lock lock;
		// 第二个判断不可以去掉,如果去掉,Lock 相当于摆设
		if(m_instance == nullptr){
			m_instance = new Singleton();
		}
	}
	return m_instance;
}

版本四: 完美解决方案,在类中对 m_instance 进行声明 volatile static Singleton* m_instance;防止变量被编译器优化到寄存器

Singleton* Singleton::getInstance(){
	if(m_instance == nullptr){
		// 如果没有第二个判断,这行代码可能会有多个线程执行到
		Lock lock;
		// 第二个判断不可以去掉,如果去掉,Lock 相当于摆设
		if(m_instance == nullptr){
			m_instance = new Singleton();
		}
	}
	return m_instance;
}

参考文章:https://blog.csdn.net/lvyibin890/article/details/81946863

二、单例模式的优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例

  • 避免对资源的多重占用(比如写文件操作)

三、单例模式的缺点

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

四、使用场景

  • 要求生产唯一序列号。

  • WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来

  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的温柔香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值