23种设计模式(十一)对象性能之单件模式

    科技2022-07-17  131

    本系列所有文章来自李建忠老师的设计模式笔记,系列如下: 设计模式(一)面向对象设计原则 23种设计模式(二)组件协作之模板方法 23种设计模式(三)组件协作之策略模式 23种设计模式(四)组件协作之观察者模式 23种设计模式(五)单一职责之装饰模式 23种设计模式(六)单一职责之桥模式 23种设计模式(七)对象创建之工厂方法 23种设计模式(八)对象创建之抽象工厂 23种设计模式(九)对象创建之原型模式 23种设计模式(十)对象创建之构建器 23种设计模式(十一)对象性能之单件模式 23种设计模式(十二)对象性能之享元模式 23种设计模式(十三)接口隔离之门面模式 23种设计模式(十四)接口隔离之代理模式 23种设计模式(十五)接口隔离之适配器 23种设计模式(十六)接口隔离之中介者 23种设计模式(十七)状态变化之状态模式 23种设计模式(十八)状态变化之备忘录 23种设计模式(十九)数据结构之组合模式 23种设计模式(二十)数据结构之迭代器 23种设计模式(二十一)数据结构之职责链 23种设计模式(二十二)行为变化之命令模式 23种设计模式(二十三)行为变化之访问器 23种设计模式(二十四)领域规则之解析器

    文章目录

    动机模式定义要点总结

      之前所述的方法都是在通过抽象的设计来实现松耦合的设计。面向对象很好地解决了“抽象”的问题,但是不可避免地要付出一定的代价。比如抽象里面有虚函数和继承,虚函数带来的内存的消耗还是比较多的。

      对于通常情况来讲,这些消耗都可以忽略不计,但是在某些情况下,面向对象所带来的成本必须谨慎处理。

    动机

      在软件系统中,经常有这样一些特殊类,必须保证它们在系统中只存在一个实例,才能确保他们的逻辑正确性,以及良好的效率。

      如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?

    Singleton.cpp 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) { m_instance = new Singleton(); } } return m_instance; }

      上述这种实现仍然会存在一些问题。线程是在指令层次抢时间片,也就是m_instance = new Singleton();这一行代码可能会先分配内存,然后把内存地址给m_instance,然后再执行构造器。这样的话就会存在问题,当线程1还没有执行构造函数,但是已经有了地址m_instance之后,另外一个线程进来发现m_instance不是nullptr就直接返回了这个地址。但是这个地址是不能用的,因为还没有调用构造器。

      解决办法:

    //C++ 11版本之后的跨平台实现 (volatile) std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release);//释放内存fence m_instance.store(tmp, std::memory_order_relaxed); } } return tmp; }

    模式定义

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

    要点总结

      Singleton模式中的实力构造器可以设置为protected以允许子类派生。

      Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。

    Processed: 0.011, SQL: 8