设计模式浅析——单例模式

1. 单例模式(Singleton)定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。

2. 单例模式结构图

3. 单例模式几种实现方式(上代码)

class Singleton
{
private:
    Singleton();                                       //构造函数私有(外界不能使用)
    Singleton(const Singleton& other);   //拷贝构造函数私有

public:
    static Singleton* getInstance();         
    static Singleton* m_instance;            //静态成员变量
};

Singleton* Singleton::m_instance = nullptr;

1. 非安全版本(适用于单线程)
Singleton* Singleton::getInstance()
{
    if (m_instance == nullptr)
    {
        m_instance = new(std::nothrow) Singleton();
        if (m_instance == nullptr)
        {
            return nullptr;
        }
    }

    return m_instance;
}

2. 线程安全版本 读操作线程浪费
Singleton* Singleton::getInstance()
{
    std::mutex mtx;                                 //函数结束时锁资源释放
    if (m_instance == nullptr)
    {
        m_instance = new(std::nothrow) Singleton();
        if (m_instance == nullptr)
        {
            return nullptr;
        }
    }

    return m_instance;
}

//3. 双检查锁(double check lock) 内存读写reorder不安全
Singleton* Singleton::getInstance()
{
    if (m_instance == nullptr)
    {
        std::mutex mtx;           //函数结束时锁资源释放
        m_instance = new(std::nothrow) Singleton();
        if (m_instance == nullptr)
        {
            return nullptr;
        }
    }

    return m_instance;
}

问题分析: 

我们默认或假定的构造顺序一般如下: 
1).  分配内存
2).  调用构造器
3).  指针返回值给instance

实际reorder后的顺序可能为:
1. 分配内存
2. 指针返回值给instance
3. 调用构造器

导致的问题:

当一个线程执行到第二步时,假如此时有另外一个线程访问,会默认m_instance不为空返回,此时实际还未调用构造器,进而导致不可预知的问题。

 

4. c++11版本之后的跨平台实现(volatile)
std::atomic<Singleton*> Singleton::m_instance;   //原子对象
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance()
{
    Singleton* s = m_instance.load(std::memory_order_relaxed);  //屏蔽编译器的reorder
    std::_Atomic_thread_fence(std::memory_order_acquire);         //获取内存fence
    if (s == nullptr)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        s = m_instance.load(std::memory_order_relaxed);  //取变量
        if (s == nullptr)
        {
            s = new Singleton;   //保证不出现reorder
            std::_Atomic_thread_fence(std::memory_order_release);  //释放内存fence
            m_instance.store(s, std::memory_order_relaxed);
        }
    }

    return s;
}


4. 总结

1) 单例模式中的实例构造器可以设置为protected以允许子类派生;

2) 单例模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,违背单例模式设计初衷;

3) 注意对double check的正确实现;

5. 扩展

值得深究的点: volatile的内存屏障作用。

voldatile关键字具有“易变性”,声明为volatile变量编译器会强制要求读内存,相关语句不会直接使用上一条语句对应的的寄存器内容,而是重新从内存中读取。

其次具有”不可优化”性,volatile告诉编译器,不要对这个变量进行各种激进的优化,甚至将变量直接消除,保证代码中的指令一定会被执行,这一点可以与单例模式(double check存在问题)中reorder联系起来。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值