线程不安全版本的单例模式(详解)


出发点

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

实现

单例分为两种实现方法:

  • 懒汉

  • 第一次用到类实例的时候才会去实例化,上述就是懒汉实现。

  • 饿汉

  • 单例类定义的时候就进行了实例化。

懒汉模式的实现:

class Singleton{
public:
    static Singleton* getInstance(){
        // 先检查对象是否存在
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
        return m_instance;
    }
private:
    Singleton(); //私有构造函数,不允许使用者自己生成对象
    //关键点就是将Singleton()设置为私有的

    Singleton(const Singleton& other);
    static Singleton* m_instance; //静态成员变量 
};

Singleton* Singleton::m_instance=nullptr; //静态成员需要先类外初始化

饿汉的实现:

class singleton {
private:
    singleton() {}
    static singleton *p;
public:
    static singleton *instance();
};

singleton *singleton::p = new singleton();
singleton* singleton::instance() {
    return p;
}

解释

这是一个非常简单的实现,将构造函数声明为privateprotect防止被外部函数实例化,内部有一个静态的类指针保存唯一的实例,实例的实现由一个public方法来实现,该方法返回该类的唯一实例。

当然这个代码只适合在单线程下,当多线程时,是不安全的。考虑两个线程同时首次调用instance方法且同时检测到p是nullptr,则两个线程会同时构造一个实例给p,这将违反了单例的准则。

多线程下的分析:

正常情况下,如果线程A调用完了getInstance()之后,将m_instance初始化了,线程B再去调用getInstance()就不会在创建一个新的实例了,直接使用之前A创建好的实例;然而存在这一种情况,当线程A正在执行getInstance(),但是还没有创建好m_instance,此时B线程调用getInstance(),m_instance是nullptr,会new一个实例。A一个实例,B一个实例,这样就可能导致程序错误,同时,还会发生内存泄漏。

解决办法

使用多线程加锁、双重检查锁模式、memory barrier指令、静态局部变量、Atomic、pthread_once等方法


文章作者: AllenMirac
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AllenMirac !
  目录