- 单例模式保证系统中只有一个实例
- 享元模式既保证了细粒度的对象,又减少了内存负担
对象性能模式
- 面向对象解决了抽象问题
- 但付出了一定代价
- 在某些情况下面对象的成本要谨慎处理
- 典型模式
- Singleton
- Flyweight
单例模式
动机
- 软件系统中,有一种特殊的类,在系统中必须保证只有一个实例
- 又保证逻辑正确又及良好的性能
- 如何绕过常规构造器
- 保证一个类只有一个实例
模式定义
- 保证一个类只有一个实例,并提供该实例的全局访问点
类图
初始设计
-
单例类
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; }
- 多线程时
- 线程A执行
if (m_instance == nullptr)
时 - 线程B也执行
if (m_instance == nullptr)
判断 - 此时都是真,则线程A/B都会创建Singleton实例
- 线程A执行
- 多线程时
-
多线程版本
-
加锁
//线程安全版本,但锁的代价过高 Singleton* Singleton::getInstance() { Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }
- 加锁成本太高,即使
m_instance != nullptr
也要执行加锁和释放锁- 当
m_instance
创建成功后,就不需要再加锁操作了 - 在高并发场景下,锁的成本不可忽略
- 当
- 加锁成本太高,即使
-
双检查
Singleton* Singleton::getInstance() { if(m_instance==nullptr){ Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } } return m_instance; }
- 先查询
m_instace
是否为空- 不空不加锁,直接返回实例
- 空,则进入实例化过程
- 实例化过程还是要加锁
- 仍是防止多次实例化
- 锁前检查是用于快速返回实例操作
- 锁后检查是用于安全创建实例操作
- 双检查锁,由于编译器的reorder导致不安全
- 先查询
-
reorder的应对
m_instance = new Singleton();
- 正常流程
- 分配内存
- 调用构造器,对内存初始化
- 内存地址赋值给
m_instance
- 编译器优化(reorder)
- 分配内存
- 内存地址赋值给
m_instance
- 调用构造器,对内存初始化
- 导致的问题
- 线程A
- 分配内存
- 内存地址赋值给
m_instance
- 线程B
- 进入getInstance()
- 执行第一步:
if (m_instance == nullptr)
- 发现非空,直接返回这个
m_instance
- 但此时线程A还未执行构造函数,导致线程B得到的是未初始化的实例
- 线程A
- 正常流程
-
使用
volatile
禁止对m_instance
操作的编译器优化Singleton* Singleton::getInstance() { volatile m_instance; if(m_instance==nullptr){ Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } } return m_instance; }
-
享元模式
动机 Motiveation
- 使用面向对象的技术,会使系统中很快充满细粒度的对象
- 带来很高的运行时代价
- 主要是内存方面
- 如何避免大量细粒度对象
- 让客户端仍能使用对象编程
模式定义
- 使用共享技术,有效支持大量细粒度的对象
结构
初始设计
- 字库引擎
- 每一个字符都应该是一个字体类
- 几万的字符,就要有几万个字体类
字体类
class Font {
private:
//unique object key
string key;
//object state
//....
public:
Font(const string& key){
//...
}
};
- 通过字符key找对应的字体
重构设计
class FontFactory{
private:
map<string,Font* > fontPool;
public:
Font* GetFont(const string& key){
// 字体对象池
map<string,Font*>::iterator item=fontPool.find(key);
if(item!=footPool.end()){
return fontPool[key];
} else{
Font* font = new Font(key);
fontPool[key]= font;
return font;
}
}
void clear(){
//...
}
};
java, c++会在编译器层面实现享元模式
要点总结
- 面向对象很好的解决了抽象问题,但付出了一定代价
- flyweith解决的是代价问题
- flyweight通过共享减低系统中的对象个数
- 降低细粒度对系统的内存压力
- 注意对象状态的处理
- 多少对象数量合适,需要具体评估