单例模式中的饿汉模式和懒汉模式详解

设计模式

      设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模 式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国 之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

设计模式的目的

      为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件 工程的基石脉络,如同大厦的结构一样。

设计模式分类: 三种类型,共32种
            创建型模式: 单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
            结构型模式: 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
            行为型模式: 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、
                                   解释器模式、状态模式、策略模式、职责链模式、访问者模式。

在这里主要分析单例模式

单例模式:

         一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该 实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对 象统一读取, 然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:
(一)饿汉模式: 就是说不管你将来也不要,程序启动时就创建一个唯一的实例对象。
        代码实现如下:

class Singleton
{
public:
 Singleton& GetInstrance()
 {
  return _ps;
 }
private:
 Singleton()
 {}
private:
 static Singleton _ps;
private:
 Singleton(Singleton &s) = delete;//或者写成这种 Singleton(Singleton & s);只声明,不定义
};
Singleton Singleton::_ps;

分析:如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

(二)懒汉模式:

        如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊,初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。所以这种情况使用懒汉模式 (延迟加载)更好。

代码实现如下:

#include
using  namespace std;
class Singleton
{
public:
 static Singleton* GetInstrance()
 {
  if (nullptr == _ps)
  {
   _ps = new Singleton();
   cout << "Creat New Obj."<<endl;
  } 
  return _ps;
 }
private:
 Singleton()
 {}
private:
 Singleton(const Singleton&);
private:
 static Singleton * _ps;
};
 Singleton * Singleton:: _ps = nullptr;
int main()
{
   Singleton::GetInstrance();
   Singleton::GetInstrance();
   Singleton::GetInstrance();
   return 0;
}

代码分析:如果在单线程环境没什么问题,但是多线程就会出错,所以要进行下一步优化。

#include
#include
#include
using  namespace std;
class Singleton
{
public:
 static Singleton* GetInstrance()
 {
  if (nullptr == _ps)
  {
   _mutex.lock();//容易线程阻塞,所以执行锁加双检测
   if (nullptr == _ps)
   {
    _ps = new Singleton();//申请空间  构造对象  给对象赋值(这三个顺序可能打乱,可能会先赋值,再构造)
    cout << "Creat New Obj." << endl;
   }
   _mutex.unlock();
  }
  return _ps;
 }
 /*
 static void Realease()//释放空间 
 {
  if (nullptr == _ps)
  {
   _mutex.lock();
   if (nullptr == _ps)
   {
    delete _ps;
    _ps = nullptr;
   }
   _mutex.unlock();
  }
 }
 */
 class GC//创建一个内部类,负责清理资源
 {
 public:
  ~GC()
  {
   if (nullptr == _ps)
   {
     delete _ps;
     _ps = nullptr;
   }
  }
 };
private:
 Singleton()
 {}
private:
 Singleton(const Singleton&);
private:
 static Singleton * volatile _ps;//1 不要从寄存器里面拿,去内存去拿 2 阻止编译器对代码优化,打乱代码的执行流程(次序错乱)
 static mutex _mutex;
 static GC _gc;
};
Singleton * volatile Singleton::_ps = nullptr;
mutex Singleton::_mutex;
 Singleton::GC _gc;
void func(int a)
{
 Singleton::GetInstrance();
}
int main()
{
 thread t1(func , 10);
 thread t2(func, 10);
  t1.join();
  t2.join();
  cout << Singleton::GetInstrance() << endl;
  cout << Singleton::GetInstrance() << endl;
 return 0;
}

代码分析:

1 防止线程阻塞和保证一个时间片只能有一个线程访问,采用加双锁检测。
2 _ps = new Singleton();的应该顺序为:申请空间、构造对象、给对象赋值。但是可能编译器会对其优化, 打乱顺序。所以_ps指针前面加volatile关键字,去寄存器拿,保证内存的可见性。
3 以上的Realease()函数释放空间会存在一些问题: (1) 用户可能忘记调用该函数,会造成内存泄漏(2)无法保证其他线程已经用完了。基于以上两点问题,创建一个内部类,负责清理资源。

你可能感兴趣的:(c++,数据结构)