设计模式学习总结:(10)单例模式探讨

单例模式(Singleton)很简单,从名字也很容易知道解决的是唯一对象创建问题,很多时候,如果因为一个对象只需要存在一份,正常对象创建方式有种杀鸡用牛刀的感觉。同时,也不能假设用户素质足够高,至少我们要保证从语法上,多个对象存在是不合理的,我们所要做的,就是约束对方的行为。

意图:

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


在c++中为了限定对象的创建,我们需要把构造函数设置为私有,保证无法从外界构造,同时需要一个静态变量指针来保存唯一对象,最后至少还需要一个函数来获得这个唯一对象。

class Singleton{
private:
    Singleton()=default;

    static Singleton* _instance;
public:
    static Singleton* getInstance();

};

Singleton* Singleton::m_instance=nullptr; //c++11 nullptr

Singleton* Singleton::getInstance() {
    if (_instance == nullptr) {     //多线程触发点
        _instance = new Singleton();
    }
    return _instance;
}

李建忠老师的设计模式这里让我大开眼界,送上笔记一枚:

-------------------------------------------------------------note------------------------

这种实现方式在单线程下,已经很好了,但是再多线程下,存在安全隐患。

在多线程情况下,如果多个线程同时执行到  if  判断那里,当第一个线程进入判断,然后第二个线程也进入判断,依次类推,可能有多个线程进入判断,这样就造成多个实例被new出来,而最终只有一个能被获得,剩下的将成为内存泄露的一分子。所以,我们很自然的想到用线程锁来解决,假设有这样一个线程锁,我们可以这样实现:

Singleton* Singleton::getInstance() {
    Lock lock;

    if (_instance == nullptr) {
        _instance = new Singleton();
    }

    lock.relase();
    return _instance;

}

但是又考虑到,if判断,只有有限的一次可能执行到,剩下大超级大的一部分是不可能进入判断的,也就是说,仅仅为了有限的o(1)次,我们就每一次创建一个锁,然后释放,如果次数足够多,并发量足够大,效率有很大的影响。很自然是不允许的,对于很多需要效率的场景,所以有了曾经风靡一时的双重锁解法。

Singleton* Singleton::getInstance() {

    if(_instance==nullptr)
    {
        Lock lock;
        if (_instance == nullptr)
        {
            _instance = new Singleton();
        }
        lock.realse();
    }
    return _instance;
}

保证了在足够多次的情况下,都不会获得锁,即使有幸获得锁,我们在进行判断,反正漏网之鱼,其实这代码从高级语言层面上看已经很完美了,然而,这里面竟然存在安全隐患。

叫做内存reorder隐患。 参考资料:链接

简单的说,_instance = new Singleton() 我们这一句的理想顺序是 创建空间->构造对象->赋值给指针。但是底层基于效率考虑,可能会编译成这样一种执行顺序,就是 创建空间->赋值给指针->构造对象。这样就造成如果刚好某个线程执行到了,赋值给指针,这一步,然后切换到另一个线程它判断,发现指针不是null,于是它就直接返回对象,注意,这个时候它返回了一个还未构造属性的对象。这就是问题所在。

最后,附上c++11新标准的解决方案,直接复制代码过来。

std::atomic<Singleton*> Singleton::_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = _instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = _instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

--------------------end----------------------------

下面写一些见解,纯思考:

----------------------------------------------------------语法思考-------------------------------------------------------------------------------------

c++类里面,static 可以定义成指针,也能定义成普通的成员。

抛出疑问:

class Singleton
{
    Singleton()=default;
    static Singleton _instance;
public:
    static Singleton getInstance();
};
Singleton Singleton::_instance = Singleton();
Singleton Singleton::getInstance()
{
    return _instance;
}

这种方式好像也能解决多线程问题,我不知道存不存在安全隐患,暂且保留,后续补充。

另外,我们对比一下一个类里面如果这三种定义:

class A
{
static A a; //合理
static A *b; //合理

A *c; //合理
A d;//不合理
}

很正常的语法,之前也做过思考,我们只要知道第四个为什么不合理,一切都明了了。在类实例被创建的时候,如果是第四种形式,你可以想象,这样的定义是需要默认初始化,或者如果你给它一个类。但是他本身是一个A实例,这个A实例也存在这样的一个A实例,于是A实例里面有个A1实例,A1实例里面有个A2实例,何时是终结。就好像山上有个庙,庙里有个和尚,和尚说:“山上有个庙。。。”。

如果是指针,那么可以默认初始化为null,这样null就不存在A实例,就不会进入和尚的庙。至于静态变量,如果你理解它的内存空间,其实不管是任何实例,都是同一个A实例,是唯一的同一个。基于此,我做过一个耐人寻味的测试:

class A
{
static A a;
public:
    int b;
}
A A::a = A();
int main()
{
    A test = A();
    test.a.a.a.a.a.b = 1;
    cout << test.a.b<<endl;
    test.a.a.a.a.a.a.a.a.a.a.a.b = 2;
    cout << test.a.b<<endl;
   return 0;
}

能猜到结果吗。

------------------------------------------------------------end--------------------------------------------------------------------------------------

时间: 2024-08-28 11:46:22

设计模式学习总结:(10)单例模式探讨的相关文章

js设计模式学习一(单例模式)

写在最前 为什么会有设计模式这样的东西存在,那是因为程序设计的不完美,需要用设计模式来弥补设计上的缺陷,那立马估计会有童鞋问,既然设计的不完美,那就换个完美点的语言,首先,没有绝对完美的语言存在,其次,借鉴下前辈说的话,灵活而简单的语言更能激发人们的创造力,所以生命力旺盛,这也能够解释,近些年来前端发展的如此迅速的原因吧. ps一段,自从开始正儿八经深入学习前端已经有一年多左右了,当时定的一个看书目标就是最初的是dom入门,之后是高三书和犀牛书,截止到现在这三本基本都算看完了,犀牛书后续的一些章

php 设计模式学习笔记之单例模式

1 // 单利模式(三私一公) 2 3 class Singleton{ 4 5 //私有化构造方法,禁止外部实例化 6 7 private function __construct(){} 8 9 //私有化__clone,防止被克隆 10 11 private function __clone(){} 12 13 //私有化内部实例化的对象 14 15 private static $instance = null; 16 17 // 公有静态实例方法 18 19 public static

java/android 设计模式学习笔记(10)---建造者模式

这篇博客我们来介绍一下建造者模式(Builder Pattern),建造者模式又被称为生成器模式,是创造性模式之一,与工厂方法模式和抽象工厂模式不同,后两者的目的是为了实现多态性,而 Builder 模式的目的则是为了将对象的构建与展示分离.Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程.一个复杂的对象有大量的组成部分,比如汽车它有车轮.方向盘.发动机.以及各种各样的小零件,要将这些部件装配成一辆汽车,这个装配过

java/android 设计模式学习笔记(一)---单例模式

前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使用的时候也会有一些坑. PS:对技术感兴趣的同鞋加群544645972一起交流 设计模式总目录 java/android 设计模式学习笔记目录 特点 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式的使用很广泛,比如:线程池(threadpool).缓存(cache).对

【我的设计模式学习】单例模式

单例模式大概是最直观的一种设计模式了.尽管直观却不简单. 数学与逻辑学中,singleton定义为"有且仅有一个元素的集合".单例模式可以如下定义:"一个类有且仅有一个实例,并且自行实例化向整个系统提供". 我比较喜欢Design Patterns 一书中的描述"保证一个类仅有一个实例,并提供一个访问它的全局访问点". 单例模式的特点 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例

设计模式学习01—单例模式

一.动机与定义 系统中有些资源只能有一个,或者一个就够,多个浪费.例如一个系统只能有一个窗口管理器或文件系统.一个系统只能有一个计时器或序号生成器.web系统只能有一个页面计数器等等.此时,最好就需要把这些资源设置成有且仅有一个实例. 代码中也就是如何保证一个类只有一个实例并且这个实例能够被访问呢?只有一个实例的就意味着不能让其他类来实例化,也就是只能自己实例化自己.能够被访问也就意味着自身要对外提供全局方法来获取到这个实例,这就是单例模式. 单例模式定义:确保某一个类只有一个实例,而且自行实例

python之路,Day24 常用设计模式学习

python之路,Day24 常用设计模式学习 本节内容 设计模式介绍 设计模式分类 设计模式6大原则 1.设计模式介绍 设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一

设计模式学习总结

本文是对各处设计模式示例的总结概括和简化,主要参考 http://blog.csdn.net/zhangerqing/article/details/8194653 直接看本文估计比较枯燥无聊,因为没图~~??,建议对设计模式有兴趣的先看看上面的博文,或者基础比较好可直接移到最底下看下我的各模式一句话概括总结,有什么意见建议欢迎提出~~~~~~~~~~ 总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式.结构型模式,共七种:适配器模式.装饰

设计模式学习总结系列应用实例

1.单例模式 应用实例:1.一个党只能有一个主席.2.Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行.3.一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件. 2.工厂模式 应用实例:1.你需要一辆汽车,你可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现 2.Hibernate换数据库只需换方言和驱动

java/android 设计模式学习笔记(5)---对象池模式

这次要介绍一下对象池模式(Object Pool Pattern),这个模式为常见 23 种设计模式之外的设计模式,介绍的初衷主要是在平时的 android 开发中经常会看到,比如 ThreadPool 和 MessagePool 等. 在 java 中,所有对象的内存由虚拟机管理,所以在某些情况下,需要频繁创建一些生命周期很短使用完之后就可以立即销毁,但是数量很大的对象集合,那么此时 GC 的次数必然会增加,这时候为了减小系统 GC 的压力,对象池模式就很适用了.对象池模式也是创建型模式之一,