初步理解单例模式

目录

  1. 1. 简单的例子
    1. 1.1. 程序启动时加载
    2. 1.2. 懒加载-单线程
    3. 1.3. 懒加载-多线程

今天面试的时候被问到了这个问题,自觉答的不是很好,第一次没有答到点子上,故简单巩固一下。

tx 和 yfd 的客户端都问到了这个问题。

为啥要用单例:

如果我们定义的类必须得在这个进程中独一无二,或是没有必要创建多个实例,那么就应该使用单例的模式去构建这个类。

实际应用:

程序中提供 Redis 访问/存储的服务类,创建一次,分发给各个模块自行调用即可。如果创建多个实例,没啥意义,只是徒占内存。

简单的例子

程序启动时加载

#include<iostream>

class SignleTon{
private:
    SignleTon(){};
public:
    SignleTon& Factory(){
        static SignleTon me;
        return me;
    }
};

这个类会在程序运行的时候实例化,可以通过调用 SignleTon::Factory() 来获取一个类的实例。

但是如果想要在需要调用类的时候再初始化这个类呢?(懒加载)

懒加载-单线程

#include<iostream>

class SignleTon{
private:
    SignleTon(){};
public:
    SignleTon& Factory(){
        static SignleTon* me=nullptr;
        if(me == nullptr){
            me = new SignleTon();
        }
        return *me;
    }
};

现在我们的这个类只有在调用 SignleTon::Factory() 的时候才会实例化——这是好的,但是假如我们的程序有多个线程并且跑在多核处理器上的时候呢?如果恰恰好在同一时刻有两个程序调用了未初始化的 SignleTon::Factory(),那么这个类会实例化两次。更糟糕的是,如果第一次的调用时获取的引用被丢弃了,那么就会造成内存泄露——这显然不是我们希望在一个需要长时间运行程序中看到的事情。

懒加载-多线程

#include<iostream>
#include<mutex>

class SignleTon{
private:
    SignleTon(){};
public:
    SignleTon& Factory(){
        static SignleTon* me=nullptr;
        static std::mutex mtx;
        if if(me != nullptr){
            return *me;
        }
        mtx.lock();
        if(me != nullptr){
            mtx.unlock();
            return *me;
        }
        else {
            me = new SignleTon();
            mtx.unlock();
            return *me;
        }
    }
};

现在我们给这个 Factory() 做了一些小小的改动,其实就是加了一道锁,保证了在同一时刻只有一个线程在尝试创建 SignleTon 这个类,这样就不会多次实例化了。