单例模式

131 阅读4分钟
  • 全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式即为单例模式(典型应用:任务队列

饿汉模式

  • 饿汉模式即在类加载时立刻进行实例化,是线程安全的!
class TaskQueue
{
public:
    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;

    // 提供给外部的接口,用于访问唯一实例
    static TaskQueue* getInstance()
    {
        return m_taskQ;
    }

    // 提供销毁实例的方法
    static void releaseInstance()
    {
        delete m_taskQ;
        m_taskQ = nullptr;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};

// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

int main()
{
    TaskQueue* obj = TaskQueue::getInstance();

    TaskQueue::releaseInstance();

    return 0;
}

懒汉模式

  • 懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化,并非线程安全的。
class TaskQueue
{
public:
    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    // 提供给外部的接口,用于访问唯一实例
    static TaskQueue* getInstance()
    {
        if(m_taskQ == nullptr)
        {
            m_taskQ = new TaskQueue;
        }
        return m_taskQ;
    }

    // 提供销毁实例的方法
    static void releaseInstance()
    {
        delete m_taskQ;
        m_taskQ = nullptr;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};

// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = nullptr;

注:单线程情况下调用getInstance()函数获取单例对象是没有问题的,但若是多线程情况,多个线程同时执行getInstance()函数,这时每个线程都会new出一个实例对象,与单例模式的定义相悖,故懒汉模式存在线程安全问题!

二者的区别

  • 懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。

利用静态局部变量(C++11)的懒汉模式

  • C++11保证静态局部变量的初始化是线程安全!
#include <iostream>

class Singleton {
private:
    Singleton() {} // 私有构造函数

public:
    // 禁止拷贝和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance; // 静态局部变量,线程安全 (C++11 保证)
        return instance;
    }

    void doSomething() {
        std::cout << "Static Local Variable Singleton instance" << std::endl;
    }
};

int main() {
    Singleton& singleton = Singleton::getInstance();
    singleton.doSomething();
    return 0;
}

结合智能指针的懒汉模式

使用智能指针来管理静态局部对象,真正的Singleton对象是动态分配在堆上,unique_ptr负责管理它的生命周期。 而使用静态局部对象Singleton对象存放在静态存储区。 使用上两者差不多,但智能指针版本可以自定义删除其、统计分配信息等。

#include <iostream>
#include <memory>

class Singleton {
private:
    // 私有构造函数,防止外部实例化
    Singleton() = default;

public:
    // 禁止拷贝和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static std::unique_ptr<Singleton> instance(new Singleton());
		return *instance;
    }

    void doSomething() {
        std::cout << "Static Local Variable Singleton instance" << std::endl;
    }
};

int main() {
    Singleton& singleton = Singleton::getInstance();
    singleton.doSomething();
	
    return 0;
}

基于饿汉模式的任务队列

// 基于饿汉模式的线程安全的任务队列
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>

class TaskQueue
{
public:
    // 提供给外部的接口,用于访问唯一实例
    static TaskQueue* getInstance()
    {
        return &m_obj;
    }

    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;

    // 任务队列是否为空
    bool isEmpty()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        bool flag = m_taskQ.empty();
        return flag;
    }

    // 添加任务
    void addTask(int data)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        m_taskQ.push(data);
    }

    // 取出任务
    int takeTask()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            return m_taskQ.front();
        }
        return -1;
    }

    // 删除任务
    bool popTask()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            m_taskQ.pop();
            return true;
        }
        return false;
    }

private:
    TaskQueue() = default;
    static TaskQueue m_obj;
    std::queue<int> m_taskQ;
    std::mutex m_mutex; // 互斥锁,用以确保queue操作的线程安全
};

// 静态成员初始化放到类外部处理
TaskQueue TaskQueue::m_obj;

int main()
{
    std::thread t1([](){
        TaskQueue* taskQ = TaskQueue::getInstance();
        for (int i = 0; i < 100; ++i)
        {
            taskQ->addTask(i + 100);
            std::cout << "+++push task: " << i + 100 << ", threadID: " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
    });

    std::thread t2([](){
        TaskQueue* taskQ = TaskQueue::getInstance();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        while (!taskQ->isEmpty())
        {
            int data = taskQ->takeTask();
            std::cout << "---task task: " << data << ", threadID: " << std::this_thread::get_id() << std::endl;
            taskQ->popTask();
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    });

    t1.join();
    t2.join();

    return 0;
}