单例模式
定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ——《设计模式》GoF
代码–版本1
// 内存栈区 // 内存堆区 // 常数区 // 静态区 系统释放 // 二进制代码区 class Singleton { public: static Singleton * GetInstance() { if (_instance == nullptr) { _instance = new Singleton(); } return _instance; } private: Singleton(){}//构造 Singleton(const Singleton &clone){} //拷⻉构造 Singleton& operator=(const Singleton&) {}//可能会赋值,也要设置成private static Singleton * _instance;//变量需要声明为静态 } Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
版本1的问题与解决技巧
- 多线程有问题1
- 单线程内存泄漏,内存释放有问题2
- 静态区是系统帮我们释放内存,系统会帮我们释放静态区_instance的指针,但不会释放内存堆区的_instance = new Singleton(),new的对象
- 如果说加入一个析构函数,那么谁来调用析构函数?
代码–版本2
class Singleton { public: static Singleton * GetInstance() { if (_instance == nullptr) { _instance = new Singleton(); atexit(Destructor); } return _instance; } ~Singleton() {} private: static void Destructor() {//private保护 if (nullptr != _instance) { delete _instance; _instance = nullptr; } } Singleton();//构造 Singleton(const Singleton &cpy); //拷⻉构造 Singleton& operator=(const Singleton&) {} static Singleton * _instance; } Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 // 还可以使用 内部类,智能指针来解决; 此时还有线程安全问题 //public: // class GC { // ~GC(){ // delete _instance //};
版本2的问题与解决技巧
- 程序退出的时候atexit(Destructor)可帮我们解决内存释放问题
- 内部类方法可以考虑加一个static GC gc对象 ,初始化Singleton::GC gc ;gc对象也存储在静态区系统自动释放,它释放时系统会调用~GC()析构函数
- 借助静态区解决了堆内存释放,对象可以自己调用析构函数指针要手动调用还是要思考多线程问题
代码–版本3
#include <mutex> class Singleton { // 懒汉模式 lazy load public: static Singleton * GetInstance() {//std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换线程 if (_instance == nullptr) { std::lock_guard<std::mutex> lock(_mutex); // 3.2 if (_instance == nullptr) { _instance = new Singleton(); atexit(Destructor); } } return _instance; } private: static void Destructor() { if (nullptr != _instance) { delete _instance; _instance = nullptr; } } Singleton(){} //构造 Singleton(const Singleton &cpy){} //拷⻉构造 Singleton& operator=(const Singleton&) {} static Singleton * _instance; static std::mutex _mutex; } Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化
版本3的问题与解决技巧
- 3.1 处互斥锁有什么问题?系统会切换线程锁力度大,系统调用开销大,我们只是读_instance这把锁应该要恰到时机只需要在_instance = new Singleton();new对象时加锁 问题1
- new分为三个步骤:1.分配内存 2.调用构造函数 3.赋值操作 多线程环境下cpu进行重排,cpu指令reorder 问题2
- 加锁为什么需要双检测?不是一个原子操作晚到的线程_instance 不等于nullptr 就直接进行返回了,可能没有调用构造函数:如不希望的顺序CPU执行顺序1 3, 2
- 问题3多线程双检测后到的线程有问题
- coredump不是空指针,类成员变量 奇怪的值
- 3.1没有3.2的问题但是3.1效率低
- once_flag里init实现
代码–版本4
#include <mutex> #include <atomic> class Singleton { public: static Singleton * GetInstance() { Singleton* tmp = _instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障 if (tmp == nullptr) { std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release);//释放内存屏 障 _instance.store(tmp, std::memory_order_relaxed); atexit(Destructor); } } return tmp; }private: static void Destructor() { Singleton* tmp = _instance.load(std::memory_order_relaxed); if (nullptr != tmp) { delete tmp; } } Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} static std::atomic<Singleton*> _instance; static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance;//静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化 // g++ Singleton.cpp -o singleton -std=c++11
版本4的问题与解决技巧
- 解决内存reorder操作
- 内存屏障解决cpu 指令reorder
- java 里面有valitile
- 版本1.2.3是懒汉模式这样不会在一启动的时候就加载过多的内存
代码–版本5
// c++11 magic static 特性:如果当变量在初始化的时候,并发同时进入声明语句,并发 线程将会阻塞等待初始化结束。 class Singleton { public: ~Singleton(){} static Singleton& GetInstance() { static Singleton instance; return instance; } private: Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} }; // 继承 Singleton // g++ Singleton.cpp -o singleton -std=c++11 /*该版本具备 版本5 所有优点: - 利用静态局部变量特性,延迟加载; - 利用静态局部变量特性,系统自动回收内存,自动调用析构函数; - 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作; - c++11 静态局部变量初始化时,具备线程安全; */
版本5的问题与解决技巧
- 静态局部变量
- 懒汉模式
- 系统自动释放
- c++11 static自带线程安全
- 那问题来了怎么继承?构造函数私有怎么继承嘞
代码–版本6
template<typename T> class Singleton { public:static T& GetInstance() { static T instance; // 这里要初始化DesignPattern,需要调用 //DesignPattern 构造函数,同时会调用父类的构造函数。 return instance; } protected: virtual ~Singleton() {} Singleton() {} // protected修饰构造函数,才能让别人继承 Singleton(const Singleton&) {} Singleton& operator =(const Singleton&) {} }; class DesignPattern : public Singleton<DesignPattern> { friend class Singleton<DesignPattern>; // friend 能让 Singleton<T> 访 //问到 DesignPattern构造函数 private: DesignPattern(){} DesignPattern(const DesignPattern&) {} DesignPattern& operator=(const DesignPattern&) {} }
版本6的问题与解决技巧
- 能不能在Singleton中修改protected为private并加入friend class DisignPattern呢?可以但是不能这样做子类拓展性不好了,不然每一个子类都要这样做
- 非常巧妙protected
要点
- 变化的点应该依赖于稳定的点
- 封装变化点,识别出稳定点与变化点
- 单一职责原则
- 掌握比较深入
结构图