单例模式

目录

什么是单例模式?

单例模式属于简单设计模式的一种。在整个系统的生命周期内,单例类有且只有唯一一个对象,典型的应用比如日志的句柄。使用单例模式时需要考虑线程安全的问题,具体看后文具体的代码解析。

单例模式的特点

  • 单例类只能有一个实例。
  • 成员是私有的、静态的。
  • 禁止拷贝、赋值,构造函数、私有函数是私有的。

单例模式的实现方式

  • 懒汉模式:在需要使用的时候才实例化对象。
  • 饿汉模式:在系统刚启动时就实例化对象。

懒汉模式

实现一(非线程安全)

#include <iostream>
#include <array>
#include <thread>
#include <mutex>

#define MAX_THREAD_SIZE 10

class Singleton
{
public:
    static Singleton* GetInstance();
    static void DeleteInstance();
    void Print(int index);

    Singleton(const Singleton& kSingleton) = delete;
    Singleton* operator=(const Singleton& kSingleton) = delete;
private:
    Singleton();
    ~Singleton();

    static Singleton* singleton_;
};

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}

Singleton* Singleton::GetInstance()
{
    if (!singleton_)
    {
        singleton_ = new(std::nothrow) Singleton;
    }
    
    return singleton_;
}

void Singleton::DeleteInstance()
{
    if (singleton_)
    {
        delete singleton_;
        singleton_ = nullptr;
    }
}

void Singleton::Print(int index)
{
    std::cout << "线程" << index << ":" << this << std::endl;
}

Singleton* Singleton::singleton_ = nullptr;

int main()
{
    std::array<std::thread, MAX_THREAD_SIZE> threads;
   
    for (int i = 0; i < MAX_THREAD_SIZE; i++)
    {
        threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
        threads[i].join();
    }

    Singleton::DeleteInstance();

    return 0;
}

此种实现方式,可能会有两个线程同时进入GetInstance()函数,恰好同时判断出singleton_指针为空,各自new了一个Singleton对象,所以是非线程安全的,如果想要此种实现是线程安全的,那么对GetInstance()实现加上锁保护即可,详见实现二。

实现二(线程安全)

#include <iostream>
#include <array>
#include <thread>
#include <mutex>

#define MAX_THREAD_SIZE 10

class Singleton
{
public:
    static Singleton* GetInstance();
    static void DeleteInstance();
    void Print(int index);

    Singleton(const Singleton& kSingleton) = delete;
    Singleton* operator=(const Singleton& kSingleton) = delete;
private:
    Singleton();
    ~Singleton();

    static Singleton* singleton_;
    static std::mutex mutex_;
};

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}

Singleton* Singleton::GetInstance()
{
    if (!singleton_)
    {
        std::unique_lock<std::mutex> lock(mutex_);
        if (!singleton_)
        {
            singleton_ = new(std::nothrow) Singleton;
        }
    }
    
    return singleton_;
}

void Singleton::DeleteInstance()
{
    std::unique_lock<std::mutex> lock(mutex_);
    if (singleton_)
    {
        delete singleton_;
        singleton_ = nullptr;
    }
}

void Singleton::Print(int index)
{
    std::cout << "线程" << index << ":" << this << std::endl;
}

Singleton* Singleton::singleton_ = nullptr;
std::mutex Singleton::mutex_;

int main()
{
    std::array<std::thread, MAX_THREAD_SIZE> threads;
   
    for (int i = 0; i < MAX_THREAD_SIZE; i++)
    {
        threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
        threads[i].join();
    }

    Singleton::DeleteInstance();

    return 0;
}

通过在GetInstance()函数中添加锁的保护,可以保证有且只有一个线程进入并创建了Singleton类对象,从而保证了线程安全,但是多了锁的开销,那么有没有更好的方法呢?下面介绍C++11后最推荐的方式。

实现三(线程安全、推荐)

#include <iostream>
#include <array>
#include <thread>
#include <mutex>

#define MAX_THREAD_SIZE 10

class Singleton
{
public:
    static Singleton& GetInstance();
    void Print(int index);

    Singleton(const Singleton& kSingleton) = delete;
    Singleton& operator=(const Singleton& kSingleton)= delete;
private:
    Singleton();
    ~Singleton();
};

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}

Singleton& Singleton::GetInstance()
{
    static Singleton singleton_;
    return singleton_;
}

void Singleton::Print(int index)
{
    std::cout << "线程"  << index << ":" << this << std::endl;
}

int main()
{
    std::array<std::thread, MAX_THREAD_SIZE> threads;
   
    for (int i = 0; i < MAX_THREAD_SIZE; i++)
    {
        threads[i] = std::thread(&Singleton::Print, &Singleton::GetInstance(), i);
        threads[i].join();
    }

    return 0;
}

此种实现适用于C++11之后的程序,因为C++11规定:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。 这种返回局部静态变量的方式,更加的简洁高效,所以比较推荐。

饿汉模式

#include <iostream>
#include <array>
#include <thread>
#include <mutex>

#define MAX_THREAD_SIZE 10

class Singleton
{
public:
    static Singleton* GetInstance();
    static void DeleteInstance();
    void Print(int index);

    Singleton(const Singleton& kSingleton) = delete;
    Singleton& operator=(const Singleton& kSingleton) = delete;
private:
    Singleton();
    ~Singleton();

    static Singleton* singleton_;
};

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}

Singleton* Singleton::GetInstance()
{
    return singleton_;
}

void Singleton::DeleteInstance()
{
    if (singleton_)
    {
       delete singleton_;
       singleton_ = nullptr; 
    }
}

void Singleton::Print(int index)
{
    std::cout << "线程" << index << ":" << this << std::endl;
}

Singleton* Singleton::singleton_ = new(std::nothrow) Singleton;

int main()
{
    std::array<std::thread, MAX_THREAD_SIZE> threads;
   
    for (int i = 0; i < MAX_THREAD_SIZE; i++)
    {
        threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
        threads[i].join();
    }

    Singleton::DeleteInstance();

    return 0;
}

饿汉模式的实现,在程序启动时,就已经实例化了Singleton对象,因此,后续访问的都是同一个对象,是天然的线程安全的。

总结

单例模式是一种比较经典、常用的设计模式,面试也经常会问到,是一定要掌握的。如果在程序中需要创建一个唯一存在的实例对象,那么一定要考虑使用单例模式,优先使用懒汉模式中的返回局部静态变量的方法,切记保证线程安全。

千百度
© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容