设计模式5:“对象性能”模式——Singleton单件模式,享元模式FlyWeight

“对象性能”模式 

  面向对象很好的解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。 

典型模式:Singleton,Flyweight:只有这两个模式不是解决抽象问题,而是解决性能问题。


Singleton单件模式 

动机(Motivation)

  •  在软件系统中,经常有这样一个特殊的类,必须保证它们在系统中只存在一个示例,才能确保他们的逻辑正确性、以及良好的效率。
  • 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
  • 这个应该类设计者的责任,而不是使用者的责任。

单件模式代码:

 1 class Singleton{
 2 private:
 3     Singleton();
 4     Singleton(const Singleton& other);
 5 public:
 6     static Singleton* getInstance();
 7     static Singleton* m_instance;
 8 };
 9 
10 Singleton* Singleton::m_instance=nullptr;
11 
12 //线程非安全版本
13 Singleton* Singleton::getInstance() {
14     if (m_instance == nullptr) {
15         m_instance = new Singleton();
16     }
17     return m_instance;
18 }
19 
20 
21 //线程安全版本,但锁的代价过高
22 Singleton* Singleton::getInstance() {
23     Lock lock;
24     if (m_instance == nullptr) {
25         m_instance = new Singleton();
26     }
27     return m_instance;
28 }
29 
30 
31 //双检查锁,但由于内存读写reorder不安全
32 Singleton* Singleton::getInstance() {
33     
34     if(m_instance==nullptr){
35         Lock lock;
36         if (m_instance == nullptr) {
37             m_instance = new Singleton();
38         }
39     }
40     return m_instance;
41 }
42 
43 //C++ 11版本之后的跨平台实现 (volatile)
44 std::atomic<Singleton*> Singleton::m_instance;
45 std::mutex Singleton::m_mutex;
46 
47 Singleton* Singleton::getInstance() {
48     Singleton* tmp = m_instance.load(std::memory_order_relaxed);
49     std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
50     if (tmp == nullptr) {
51         std::lock_guard<std::mutex> lock(m_mutex);
52         tmp = m_instance.load(std::memory_order_relaxed);
53         if (tmp == nullptr) {
54             tmp = new Singleton;
55             std::atomic_thread_fence(std::memory_order_release);//释放内存fence
56             m_instance.store(tmp, std::memory_order_relaxed);
57         }
58     }
59     return tmp;
60 }
Singleton

多线程不安全,有可能被创建多次
第一种 加锁,但锁的代价过高,对读线程的锁来说是浪费的
第二种 双锁检查(锁前锁后检查),但由于内存读写reorder不安全

reorder不安全:代码执行顺序不确定

假象顺序:1 分配内存;2 调用构造器;3 返回指针
CPU层面指令集(有可能reorder):1分配内存;2返回指针 m_instance ;3调用构造器
(执行第2步之后,另外一个线程进来发现m_instance不是null,但未执行构造器,对象状态不正确)

模式定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。
                                                                       ——《设计模式》GoF

 

 要点总结 

  • Singleton模式中的实例构造器可以设置为protected以允许子类派生。
  • Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能会导致多个对象实例,与Singleton模式的初衷相违背。
  • 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。 

享元模式FlyWeight

动机(Motivation)

  • 在软件系统中采用纯粹对象方案的问题 在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。
  • 如何在避免大量·细粒度对象问题的同事,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?

模式定义

运用共享技术有效地支持大量的细粒度对象                                                                     

                                                  ——《设计模式》GoF

享元模式代码:

 1 class Font {
 2 private:
 3 
 4     //unique object key
 5     string key;
 6     
 7     //object state
 8     //....
 9     
10 public:
11     Font(const string& key){
12         //...
13     }
14 };
15 
16 class FontFactory{
17 private:
18     map<string,Font* > fontPool; // 字体池
19     
20 public:
21     Font* GetFont(const string& key){
22 
23         map<string,Font*>::iterator item=fontPool.find(key);
24         
25         if(item!=footPool.end()){
26             return fontPool[key];
27         }
28         else{
29             Font* font = new Font(key);
30             fontPool[key]= font;
31             return font;
32         }
33     }
34     
35     void clear(){
36         //...
37     }
38 };
View Code

  一篇文章可能有上万个字,但字体确实有限很少的,不能为每个字都保存一个字体对象,因此用共享技术共享一个对象key. 

要点总结 

  • 面向对象很好的解决了抽相性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向的代价问题,一般不触及面向对象的抽象性问题。
  • Flyweight采用对象共享的做法来降低系统中的对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对像状态的处理。
  • 对象的数量太大,从而导致对像内存开销加大——什么样的数量才算大?这需要我们仔细根据具体应用情况进行评估,而不能凭空臆断。 

 

本文内容源自 :C++设计模式 Design Patterns 李建忠  课程

posted on 2017-12-09 11:32  flysong  阅读(124)  评论(0编辑  收藏  举报

导航