什么是单例模式


  单例模式最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  我们需要注意以下几点:

  1. 单例类只能有一个实例。

  2. 单例类必须自己创建自己的唯一实例。

  3. 单例类必须给所有其他对象提供这一实例。

  我们在以下环境中使用这种模式:

  当一个类在整个项目中都需要频繁的访问(全局访问),在每次调用时都要创建一次对象(频繁创建),在每次调用完后都要销毁一次(频繁销毁),那么对性能的负担特别大,这个时候,我们的单例模式就需要用在这种环境下。

  单例模式的实例代码如下(代码可以左滑查看全部):


    新建一个类 Singleton,在 Singleton.h中:
    class Singleton{
    public:
      //构造函数
      Singleton();
      //初始化单例
      static void Initialize();
      // 供外部获取的指针
      static Singleton* Get();
    private:
      // 创建单例
      static Singleton& Create();
    private:
      // 指向单例本身的静态指针
      static Singleton* SingletonPtr;
    }
    
    在 Singleton.cpp中
	
    #include "Singleton.h"
    
    // 单例的实现
    Singleton::SingletonPtr = NULL;
    void Initialize(){
        // 检查指针是否存在
        if (SingletonPtr == NULL) {
          // 不存在就创建
          SingletonPtr = Create();
        }
    }
    Singleton::Get() {
        // 初始化单例
        Initialize();
        // 返回这个指针
        return SingletonPtr;
    }
    Singleton::Create() {
        // 创建这个单例
        Singleton& SingletonRef = new Singleton();
        // 返回这个单例
        return SingletonRef;
    }
					

  我们在调用这个单例的时候只需调用这个单例的Get()函数即可。

  但是,我们的这个单例模式在多线程的情况下是不安全的,因为在多线程的情况下,存在多个线程在同一时间同时调用Get()函数,此时我们的单例指针SingletonPtr一直为空,也就会被多个线程同时创建,这样就违反了我们单例模式的设计原则。

  我们可以通过对Initialize函数加上线程锁的方式来解决这个问题,代码如下:


    // 线程锁
    static mutex mut;

    void Initialize(){
        // 上锁
        mut.lock();
        // 检查指针是否存在
        if (SingletonPtr == NULL) {
          // 不存在就创建
          SingletonPtr = Create();
        }
        // 解锁
        mut.unlock();
    }
					

  但是这样在每个线程访问的时候都要上一个锁,对资源的消耗也是不小,于是我们可以多加一层判断来解决这个问题,代码如下:


    // 线程锁
    static mutex mut;

    void Initialize(){
        if (SingletonPtr == NULL){
            // 上锁
            mut.lock();
            // 检查指针是否存在
            if (SingletonPtr == NULL) {
              // 不存在就创建
              SingletonPtr = Create();
            }
            // 解锁
            mut.unlock();
        }
    }
					

  这样加了一层判断以后,我们只需要在单例指针SingletonPtr为空的情况下才对锁进行判断,当单例指针有值的情况下则不进行上锁解锁的操作。