面向对象很好的解决了“抽象”的问题,但是必不可免的要付出一定的代价。对于通常情况来讲。面向对象的成本大都可以忽略不计。但在某些情况,面向对象所带来的成本必须谨慎处理
- 动机
- 在软件系统中,经常有一些特殊的类,必须保证他们在系统中 只存在一个实例 ,才能保证他们的逻辑正确性、以及良好的效率
- 这个是类设计者的责任,而不是使用者的责任
例子
非线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; }
|
线程安全(加锁代价高)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Singleton(){ private: Singleton(); Singleton(const Singleton& other);
public: static Singleton* getInstance(); static Singleton* m_instance; }; Singleton* Singleton::m_instance = nullptr;
Singleton* Singleton::getInstance(){ Lock lock; if(m_instance == nullptr){ m_instance = new Singleton(); } return m_instance; }
|
线程安全(加锁代价高)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Singleton(){ private: Singleton(); Singleton(const Singleton& other);
public: static Singleton* getInstance(); static Singleton* m_instance; }; Singleton* Singleton::m_instance = nullptr;
Singleton* Singleton::getInstance(){ Lock lock; if(m_instance == nullptr){ m_instance = new Singleton(); } return m_instance; }
|
双检测锁,但由于内存读写reorder不安全(不可以用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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){ Lock lock; if(m_instance == nullptr){ m_instance = new Singleton(); } } return m_instance; }
|
- reorder
- 指令序列可能会
- 可能性2
- 先分配内存
- 将内存返回
- 调用构造函数
- 有可能会返回一个未经初始化的对象
双检查锁,但由于内存读写reorder 不安全
cpp11版本之后的夸平台实现(volatile)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Singleton(){ private: Singleton(); Singleton(const Singleton& other);
public: static Singleton* getInstance(); static Singleton* m_instance; }; Singleton* Singleton::m_instance = nullptr; std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::mutex;
Singleton* Singleton::getInstance(){ Singleton* tmp = Singleton.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); 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); m_instance.store(tmp,std::memory_order_relaxed); } } return tmp; }
|
动机
保证一个类仅有一个实例,并提供一个该实例的全局访问点
UML图
要点总结
- Singleton模式中的实例构造器可以设置为protect以允许子类派生
- Singleton模式一般不要支持拷贝构造和Clone接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背
- 如何实现多线程下安全的Singleton?注意对双检查锁的正确实现