设计模式知识总结

devtools/2024/9/22 16:33:40/

单例模式

懒汉式

线程不安全的懒汉单例

class singleton {
private:singleton() {}static singleton *p;
public:static singleton *instance();void st();
};
singleton *singleton::p = nullptr;
singleton* singleton::instance() 
{if (p == nullptr)p = new singleton();return p;
}//访问方式:
class test
{
public:private:singleton  *sing;
};test::test()
{sing = singleton::instance();sing.st();  //访问方式1//orsingleton::instance().st(); //访问方式2
}

这是一个非常简单的实现,将构造函数声明为private或protect防止被外部函数实例化,内部有一个静态的类指针保存唯一的实例,实例的实现由一个public方法来实现,该方法返回该类的唯一实例。
当然这个代码只适合在单线程下,当多线程时,是不安全的。考虑两个线程同时首次调用instance方法且同时检测到p是nullptr,则两个线程会同时构造一个实例给p,这将违反了单例的准则。

使用锁

class singleton 
{
private:singleton() {}static singleton *p;static mutex lock_;
public:static singleton *instance();
};singleton *singleton::p = nullptr;
singleton* singleton::instance() 
{lock_guard<mutex> guard(lock_);if (p == nullptr)p = new singleton();return p;
}

这种写法不会出现上面两个线程都执行到p=nullptr里面的情况,当线程A在执行p = new Singleton()的时候,线程B如果调用了instance(),一定会被阻塞在加锁处,等待线程A执行结束后释放这个锁。从而是线程安全的。
但是这种写法性能非常低下,因为每次调用instance()都会加锁释放锁,而这个步骤只有在第一次new Singleton()才是有必要的,只要p被创建出来了,不管多少线程同时访问,使用if (p == nullptr) 进行判断都是足够的(只是读操作,不需要加锁),没有线程安全问题,加了锁之后反而存在性能问题。
因此引出DCL。

双重检查锁

class singleton 
{
private:singleton() {}static singleton *p;static mutex lock_;
public:singleton *instance();// 实现一个内嵌垃圾回收类class CGarbo{public:~CGarbo(){if(singleton::p)delete singleton::p;}};static CGarbo Garbo; // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
};
singleton *singleton::p = nullptr;
singleton::CGarbo Garbo;
singleton* singleton::instance() 
{if (p == nullptr) {lock_guard<mutex> guard(lock_);if (p == nullptr)p = new singleton();}return p;
}

双重检查锁在c++11之前会出现的问题

DCLP的关键在于,大多数对instance的调用会看到p是非空的,因此甚至不用尝试去初始化它。因此,DCLP在尝试获取锁之前检查p是否为空。只有当检查成功(也就是p还没有被初始化)时才会去获得锁,然后再次检查p是否仍然为空(因此命名为双重检查锁)。第二次检查是必要,因为就像我们刚刚看到的,很有可能另一个线程偶然在第一次检查之后,获得锁成功之前初始化p。
看起来上述代码非常美好,可是过了相当一段时间后,才发现这个漏洞,原因是:内存读写的乱序执行(编译器问题)。
再次考虑初始化p的那一行:

p = new singleton;

这条语句会导致三个事情的发生:

  1. 分配能够存储singleton对象的内存;
  2. 在被分配的内存中构造一个singleton对象;
  3. 让p指向这块被分配的内存。

可能会认为这三个步骤是按顺序执行的,但实际上只能确定步骤1是最先执行的,步骤2,3却不一定。问题就出现在这。
线程A调用instance,执行第一次p的测试,获得锁,按照1,3,执行,然后被挂起。此时p是非空的,但是p指向的内存中还没有Singleton对象被构造。
线程B调用instance,判定p非空, 将其返回给instance的调用者。调用者对指针解引用以获得singleton,噢,一个还没有被构造出的对象。bug就出现了。
DCLP能够良好的工作仅当步骤一和二在步骤三之前被执行,但是并没有方法在C或C++中表达这种限制。这就像是插在DCLP心脏上的一把匕首:我们需要在相对指令顺序上定义限制,但是我们的语言没有给出表达这种限制的方法。

DCLP问题在C++11中,这个问题得到了解决。
因为新的C++11规定了新的内存模型,保证了执行上述3个步骤的时候不会发生线程切换,相当这个初始化过程是“原子性”的的操作,DCL又可以正确使用了。

该实例的析构函数什么时候执行?

如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。

可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这 样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。

一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。
类CGarbo被定义为Singleton的私有内嵌类,以防该类被在其他地方滥用。

程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。

使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。

使用静态局部变量

class singleton 
{private:singleton() {}
public:singleton *instance();
};singleton *singleton::instance() 
{static singleton p;return &p;
}

利用了静态局部变量的特性:
局部静态变量只会在第一次调用该函数时被初始化,且仅能被初始化一次,即:在之后无论再调用多少次该函数,其中的局部变量均不会再初始化。

线程安全情况:
单线程下,正确。
C++11及以后的版本(如C++14)的多线程下,正确。
C++11之前的多线程下,不一定正确。
原因在于在C++11之前的标准中并没有规定local static变量的内存模型。于是乎它就是不是线程安全的了。但是在C++11却是线程安全的,这是因为新的C++标准规定了当一个线程正在初始化一个变量的时候,其他线程必须得等到该初始化完成以后才能访问它。

延伸阅读:
声明为 GetInstance 方法静态变量的单例实例,是否线程安全?

使用pthread_once

如果是在unix平台的话,除了使用atomic operation外,在不适用C++11的情况下,还可以通过pthread_once来实现Singleton。

class singleton {
private:singleton(); //私有构造函数,不允许使用者自己生成对象singleton(const singleton &other);//要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)static void init() {p = new singleton();}static pthread_once_t ponce_;static singleton *p; //静态成员变量 
public:singleton *instance() {// init函数只会执行一次pthread_once(&ponce_, &singleton::init);return p;}
};

饿汉式

饿汉式保证线程安全

class singleton 
{
private:singleton() {}static singleton *p;
public:static singleton *instance();
};// 代码一运行就初始化创建实例 ,本身就线程安全
singleton *singleton::p = new singleton();
singleton* singleton::instance() 
{return p;
}

http://www.ppmy.cn/devtools/4904.html

相关文章

【AIGC】文本与音频生成引领行业革新

AIGC技术崛起 一、AIGC技术概述二、文本生成&#xff1a;结构化与创作型并进三、实例与代码解析四、音频生成&#xff1a;语音合成技术大放异彩五、结语 在科技的浪潮中&#xff0c;人工智能与大数据的结合不断推动着时代的进步。其中&#xff0c;AIGC&#xff08;Artificial I…

Oracle imp导入数据后中文乱码

表注释和存储过程中文变成问号的问题很可能是由于字符集不匹配所导致的。在导入过程中&#xff0c;Oracle 数据库会使用数据库的默认字符集来解释文本数据。如果导入的数据中包含了其他字符集的数据&#xff0c;特别是 UTF-8 或其他非默认字符集的数据&#xff0c;那么就可能导…

【Linux】详解如何利用共享内存实现进程间通信

一、共享内存&#xff08;Shared Memory&#xff09;的认识 共享内存&#xff08;Shared Memory&#xff09;是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间&#xff0c;从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信&#xff0c;因…

【leetcode面试经典150题】63. 删除链表的倒数第 N 个结点(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

小程序如何优化搜索排名,获取曝光

在移动互联网时代&#xff0c;小程序以其便捷、轻量级的特点&#xff0c;逐渐成为用户获取服务的重要渠道。然而&#xff0c;小程序数量众多&#xff0c;如何让自己的小程序在搜索中脱颖而出&#xff0c;获取更多的曝光和流量&#xff0c;成为众多开发者关注的焦点。 一、理解…

AI人工智能讲师大模型培训讲师叶梓 大语言模型(LLM)在科学文献摘要领域的应用

大语言模型&#xff08;LLM&#xff09;在科学文献摘要领域的应用是一个前沿且迅速发展的技术趋势。通过结合GitHub上yobibyte的Compressor项目&#xff0c;我们可以深入探讨这一技术方案的潜力和实现方式。 技术背景 随着科学研究的快速发展&#xff0c;每天都有大量的科学文…

Qt实现XYModem协议(八)

1 概述 XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据&#xff0c;并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K&am…

python动态导入包

python动态导入包 在爬虫项目中&#xff0c;有时候一个python包下面包含很多spider.py的文件&#xff0c;每个文件就是一个爬虫&#xff0c;想要在程序运行的时候动态的进行导入&#xff0c;可以使用 importlib是 Python 中用于动态导入模块的标准库。它提供了一种在运行时动…