【设计模式学习笔记】单例模式

文章介绍了C++中实现单例模式的两种主要方法——饿汉模式和懒汉模式,以及它们的线程安全问题和解决方案。饿汉模式在类加载时即创建实例,保证线程安全但可能浪费内存;懒汉模式延迟实例化,但在多线程环境下需处理同步问题。C++11通过静态局部对象和原子变量提供了更安全的懒汉模式实现。
摘要由CSDN通过智能技术生成

动机

在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。

实现方式

饿汉模式

饿汉模式就是在类加载的时候立刻进行实例化

// 饿汉模式
class TaskQueue {
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        return m_taskQ;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};

TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

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

懒汉模式

饿汉模式就是在类被调用的时候进行实例化

一般写法

// 懒汉模式
class TaskQueue {
public:
    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;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;

线程安全问题

解法1:使用互斥锁,但是每次调用锁,导致程序性能下降

class TaskQueue {
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        m_mutex.lock();
        if (m_taskQ == nullptr) {
            m_taskQ = new TaskQueue;
        }
        m_mutex.unlock();
        return m_taskQ;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
    static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;

解法2:双重检查锁定

class TaskQueue {
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        if (m_taskQ == nullptr) {
            m_mutex.lock();
            if (m_taskQ == nullptr) {
                m_taskQ = new TaskQueue;
            }
            m_mutex.unlock();
        }
        return m_taskQ;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
    static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;

上面这种通过两个嵌套的 if 来判断单例对象是否为空的操作就叫做双重检查锁定。但是在多线程环境下,new有可能被打断,导致m_taskQ 指针是不为空的,但这个指针指向的内存却没有被初始化。C++11 中引入了原子变量 atomic:

class TaskQueue {
public: 
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        TaskQueue* queue = m_taskQ.load();  
        if (queue == nullptr) {
            lock_guard<mutex> locker(m_mutex);
            queue = m_taskQ.load();
            if (queue == nullptr) {
                queue = new TaskQueue;
                m_taskQ.store(queue);
            }
        }
        return queue;
    }
private:
    TaskQueue() = default;
    static atomic<TaskQueue*> m_taskQ;
    static mutex m_mutex;
};
atomic<TaskQueue*> TaskQueue::m_taskQ;
mutex TaskQueue::m_mutex;

但是这种方法实现的懒汉模式的单例执行效率更低一些。

静态局部对象

在 C++11 标准中有如下规定:

如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。

class TaskQueue{
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        static TaskQueue taskQ;
        return &taskQ;
    }
private:
    TaskQueue() = default;
};

总结

懒汉模式:

  • 缺点:线程不安全,但是C++11的局部静态对象,可以完美解决
  • 优点:减少内存浪费

饿汉模式:

  • 缺点:浪费内存
  • 优点:线程安全

参考

单例模式 - 巴基速递

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值