• 单例模式


    单例模式:

    在实现单例模式之前需要先了解一下静态(全局/局部)变量的初始化方式:

    全局变量、non-local static 变量(文件域的静态变量和类的静态成员变量)在main执行之前就已分配内存并初始化;local static 变量(局部静态变量)同样是在 main 前就已分配内存,第一次使用时初始化

    非局部静态变量一般在 main 执行之前的静态初始化过程中分配内存并初始化,可以认为是线程安全的

    C++11 保证静态局部变量的初始化过程是线程安全的

    设计模式经典GoF定义的单例模式需要满足以下两个条件:

    1)保证一个类只创建一个实例
    2)提供对该实例的全局访问点

    在设计单例模式时有亮点需要注意:

    单例模式的实现分懒汉模式和饿汉模式以及是否线程安全

    懒汉模式和饿汉模式的区别:

    懒汉模式中,直到 Instance() 被访问,才会生成实例,这种特性被称为延迟初始化(Lazy Initialization),这在一些初始化时消耗较大的情况有很大优势。 

    饿汉模式中,在编译器初始化的时候就完成了实例的创建,和上述的 Lazy Singleton 相反

    实现1:

    懒汉模式,非线程安全,堆内存

     1 #include <iostream>
     2 #include <vector>
     3 using namespace std;
     4 
     5 class Singelton {
     6 private:
     7     static Singelton *singel;
     8     //将默认构造函数、析构函数定义为私有的
     9     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
    10     Singelton(){}
    11     Singelton(const Singelton&) = delete;
    12     Singelton& operator=(const Singelton&) = delete;
    13     ~Singelton(){}//将析构函数声明成private,防止被意外delete
    14 
    15 public:
    16     static Singelton* GetInstance() {
    17         //非线程安全
    18         //如果有多个线程同时进行singel == nullptr判断,则会创建多个实例
    19         if(singel == nullptr) singel = new Singelton();
    20         return singel;
    21     }
    22 };
    23 
    24 //类静态成员需要类外初始化
    25 Singelton* Singelton::singel = nullptr;
    26 
    27 int main(void) {
    28     // Singelton cnt;//error,访问私有的默认构造函数
    29 
    30     Singelton *s1 = Singelton::GetInstance();
    31     Singelton *s2 = Singelton::GetInstance();
    32 
    33     cout << s1 << endl;
    34     cout << s2 << endl;
    35 
    36 // 输出:
    37 // 0x28053f8
    38 // 0x28053f8
    39 
    40     return 0;
    41 }
    View Code

    第一次访问 GetInstance 时才创建实例,显然是懒汉模式。如果有多个线程同时进行 singel == nullptr 判断,则会创建多个实例,所以其是非线程安全的。并且该实现中一旦创建了实例,是不可删除的

    还有一个问题是 GetInstance 为啥是 static 的:如果 GetInstance 被定义为非静态函数的话,要访问 GetInstance 则必须先创建一个实例,而要在访问 GetInstance 之前创建实例则说明该类可以创建多个实例,这显然不符合单例模式的条件

    针对实现1中的非线程安全问题,我们可以给 if 语句加个锁

    实现2:

    懒汉模式,线程安全,堆内存

     1 #include <iostream>
     2 #include <vector>
     3 #include <mutex>
     4 using namespace std;
     5 
     6 class Singelton {
     7 private:
     8     static std::mutex mtex;//静态锁
     9     static Singelton *singel;
    10     //将默认构造函数、析构函数定义为私有的
    11     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
    12     Singelton(){}
    13     Singelton(const Singelton&) = delete;
    14     Singelton& operator=(const Singelton&) = delete;
    15     ~Singelton(){}//将析构函数声明成private,防止被意外delete
    16 
    17 public:
    18     static Singelton* GetInstance() {//注意,在静态成员函数中中只能直接使用静态数据成员
    19         mtex.lock();
    20         if(singel == nullptr) singel = new Singelton();
    21         mtex.unlock();
    22 
    23         return singel;
    24     }
    25 };
    26 
    27 //类静态成员需要类外初始化
    28 Singelton* Singelton::singel = nullptr;
    29 std::mutex Singelton::mtex;
    30 
    31 int main(void) {
    32     // Singelton cnt;//error,访问私有的默认构造函数
    33 
    34     Singelton *s1 = Singelton::GetInstance();
    35     Singelton *s2 = Singelton::GetInstance();
    36 
    37     cout << s1 << endl;
    38     cout << s2 << endl;
    39 
    40 // 输出:
    41 // 0x28053f8
    42 // 0x28053f8
    43 
    44     return 0;
    45 }
    View Code

    注意:加锁是比较费时的,而按照上面的实现,每一次访问 Getlnstance 函数都要加/解锁一次,而实际上我们只需要在创建第一个实例时加锁。显然我们可以在加锁之前先判断一下是否已经创建了实例,从而避免已经创建了实例的情况下加/解锁。即双检测锁模式

    据上面的分析有

    实现3:

    懒汉模式,线程安全,堆内存

     1 #include <iostream>
     2 #include <vector>
     3 #include <mutex>
     4 using namespace std;
     5 
     6 class Singelton {
     7 private:
     8     static std::mutex mtex;//静态锁
     9     static Singelton *singel;
    10     //将默认构造函数、析构函数定义为私有的
    11     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
    12     Singelton(){}
    13     Singelton(const Singelton&) = delete;
    14     Singelton& operator=(const Singelton&) = delete;
    15     ~Singelton(){}//将析构函数声明成private,防止被意外delete
    16 
    17 public:
    18     static Singelton* GetInstance() {//注意,在静态成员函数中中只能直接使用静态数据成员
    19         //双检测锁模式
    20         if(singel == nullptr){
    21             mtex.lock();
    22             if(singel == nullptr) singel = new Singelton();
    23             mtex.unlock();
    24         }
    25         return singel;
    26     }
    27 };
    28 
    29 //类静态成员需要类外初始化
    30 Singelton* Singelton::singel = nullptr;
    31 std::mutex Singelton::mtex;
    32 
    33 int main(void) {
    34     // Singelton cnt;//error,访问私有的默认构造函数
    35 
    36     Singelton *s1 = Singelton::GetInstance();
    37     Singelton *s2 = Singelton::GetInstance();
    38 
    39     cout << s1 << endl;
    40     cout << s2 << endl;
    41 
    42 // 输出:
    43 // 0x28053f8
    44 // 0x28053f8
    45 
    46     return 0;
    47 }
    View Code

    由于 C++11 保证静态局部变量的初始化过程是线程安全的特性,我们也可以通过静态局部变量来实现线程安全

    实现4:

    懒汉模式,线程安全,自由存储区

     1 #include <iostream>
     2 #include <vector>
     3 using namespace std;
     4 
     5 class Singelton {
     6 private:
     7     Singelton(){}//将默认构造函数定义为私有的
     8     Singelton(const Singelton&) = delete;
     9     Singelton& operator=(const Singelton&) = delete;
    10     ~Singelton(){}
    11 
    12 public:
    13     static Singelton* GetInstance() {
    14         //c++11开始,局部静态变量的初始化过程是线程安全的
    15         static Singelton instance;//静态变量的生命周期是全局的,所以可以返回局部静态变量的引用
    16         return &instance;
    17     }
    18 };
    19 
    20 int main(void) {
    21     // Singelton cnt;//error,访问私有的默认构造函数
    22 
    23     Singelton *s1 = Singelton::GetInstance();
    24     Singelton *s2 = Singelton::GetInstance();
    25 
    26     cout << s1 << endl;
    27     cout << s2 << endl;
    28 
    29 // 输出:
    30 // 0x2b853d8
    31 // 0x2b853d8
    32 
    33     return 0;
    34 }
    View Code

    实现5:

    饿汉模式,线程安全,自由存储区

     1 #include <iostream>
     2 #include <vector>
     3 using namespace std;
     4 
     5 class Singelton {
     6 private:
     7     static Singelton instance;
     8     Singelton(){}//将默认构造函数定义为私有的
     9     Singelton(const Singelton&) = delete;
    10     Singelton& operator=(const Singelton&) = delete;
    11     ~Singelton(){}
    12 
    13 public:
    14     static Singelton* GetInstance() {
    15         return &instance;
    16     }
    17 };
    18 
    19 //类外声明类静态数据成员
    20 Singelton Singelton::instance;//在mian之前初始化
    21 
    22 int main(void) {
    23     // Singelton cnt;//error,访问私有的默认构造函数
    24 
    25     Singelton *s1 = Singelton::GetInstance();
    26     Singelton *s2 = Singelton::GetInstance();
    27 
    28     cout << s1 << endl;
    29     cout << s2 << endl;
    30 
    31 // 输出:
    32 // 0x489008
    33 // 0x489008
    34 
    35     return 0;
    36 }
    View Code

    注意:饿汉模式是在 main 之前初始化的,自动满足线程安全

     

  • 相关阅读:
    SOA概念误解实施要点
    Visual Studio 2008 和 .NET Framework 3.5 Service Pack 1 Beta 发布
    【翻译】使用LINQ来简化编程的7个技巧
    我对SOA的认识以及心得
    《SQL Server 2005范例代码查询辞典》出版
    Security Tutorials系列文章以及AJAX系列文章
    代朋友发招聘信息,C++程序员
    二叉树相关算法
    最近项目的一些心得(纯贴代码)
    大型互联网网站架构心得之一:分
  • 原文地址:https://www.cnblogs.com/geloutingyu/p/8640492.html
Copyright © 2020-2023  润新知