Singleton 单件模式

“对象性能”模式

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

典型模式

  • Sington
  • Flyweight

单例模式Singleton

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

动机

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

模式选择

Singleton模式典型的结构图为:

Singleton 单件模式_第1张图片

在Singleton模式的结构图中可以看到,我们通过维护一个static的成员变量来记录这个唯一的对象实例。通过提供一个staitc的接口instance来获得这个唯一的实例。

 

 

局部静态变量

这种方式很常见,实现非常简单,而且无需担心单例的销毁问题。

// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

#include 

using namespace std;

// 单例
class Singleton
{
public:
    static Singleton& getInstance()
    {
        static Singleton instance;
        return instance;
    }


private:
    Singleton() {}  // 构造函数(被保护)
    Singleton(Singleton const &);  // 无需实现
    Singleton& operator = (const Singleton &);  // 无需实现
};

#endif // SINGLETON_H

这样以来,既可以保证只存在一个实例,通过getInstance可以访问,又不用考虑内存回收的问题。

 

懒汉式/饿汉式

         懒汉式              饿汉式

Lazy 初始化,非多线程安全

非 Lazy 初始化,多线程安全

优点:第一次调用才初始化,避免内存浪费。 
 

 

优点:没有加锁,执行效率会提高。

 

 

缺点:必须加锁(在“线程安全”部分分享如何加锁)才能保证单例,但加锁会影响效率。 缺点:类加载时就初始化,浪费内存。
class Singleton{
	
public:
	static Singleton* getInstance();
	static Singleton* m_instance;

private:
	singleton();
	Singleton(const Singleton& other);	
	Singleton& operator = (const Singleton &);
}



//懒汉
 Singleton* Singleton::m_instance=nullptr ;


 //线程非安全版
 Singleton* Singleton::getInstance()
 {
	 if(m_instance==nullptr)
	 {
		 m_instance=new Singleton();
	 }
	 return m_instance;
 }
 
 //线程安全版本 ,但是锁的代价过高
  Singleton* Singleton::getInstance()
 {
	 Lock lock;             //全局加锁,直到return
	 if(m_instance==nullptr)
	 {
		 m_instance=new Singleton();
	 }
	 return m_instance;
 }
 
//***********************************************/

//饿汉
 Singleton* Singleton::m_instance=new Singleton() ;
 
 //
 Singleton* Singleton::getInstance()
 {
	
	 return 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不安全,正常new执行 是分配内存-》调用构造器进行初始化-》返回内存地址,编译器优化,reorder之后 分配内存-》返回内存地址-》调用构造器进行初始化,如果A线程返回了没有调用构造器的地址,且线程B直接使用就会出现问题

 //C++11 版本之后的跨平台实现(volatile)
 std::atomic 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 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;
 }
 

资源释放

有内存申请,就要有对应的释放,可以采用下述两种方式:

  • 主动释放(手动调用接口来释放资源)
  • 自动释放(由程序自己释放)

要手动释放资源,添加一个 static 接口,编写需要释放资源的代码:

static void DestoryInstance()
{
    if (m_instance != nullptr) {
        delete m_instance;
        m_instance= nullptr;
    }
}

然后在需要释放的时候,手动调用该接口

Singleton::getInstance()->DestoryInstance();

当程序较复杂市在哪里释放不好确定,应该让其自动释放。解决方法就是定义一个嵌套类

 //垃圾回收
class Singleton
{
public:
	static Singleton* getInstance();

 
	~Singleton()
	{
		cout<<"~Singleton ..."<

在程序运行结束时,系统会调用 Singleton 的静态成员 GC 的析构函数,该析构函数会进行资源的释放。

 

Singleton的要点总结

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

 

 

你可能感兴趣的:(Design,Pattern)