• C++设计模式之单例模式


    单例模式:

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

    应用场景:
    在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性。
    一个全局使用的类频繁地创建与销毁。

    比如:windows 资源管理器,回收站等。

    这应该是类设计者的职责,而不是使用者的职责。也就是说这个单例不应该由人来控制,而应该由代码来限制,强制单例。

     

     1 #include <iostream>
     2 #include <mutex>
     3 #include <thread>
     4 using namespace std;
     5 mutex mu;//线程互斥对象
     6 class Singleton {
     7 public:
     8     // 静态方法,供全局调用
     9     static Singleton* getInstance() ;
    10     void GetName() { cout << "我是单例" << endl; }
    11 private:
    12     //私有构造函数,不允许使用者自己生成对象
    13     Singleton() 
    14     {
    15         cout << "++++++Singleton被构造+++++" << endl; 
    16     } 
    17     Singleton(const Singleton& other);
    18     static Singleton* m_instance; //静态成员变量 
    19 };

    单例模式分为两种类型:饿汉式,懒汉式。

    饿汉式:

    1 // 饿汉式
    2 Singleton* Singleton::getInstance()
    3 {
    4     return m_instance;
    5 }
    6 // 静态成员需要先初始化
    7 Singleton* Singleton::m_instance = new Singleton();

    在访问量比较大,或者可能访问的线程比较多时,可以实现更好的性能,以空间换时间。
    因为是静态属性在单例类定义的时候就进行实例化,所以优点是线程是安全的,缺点是无论用户是否使用单例对象都会创建单例对象。

    懒汉式:

     1 //静态成员需要先初始化
     2 Singleton* Singleton::m_instance = NULL;
     3 // 懒汉式
     4 Singleton* Singleton::getInstance()
     5 {
     6 // 先检查对象是否存在
     7 if (m_instance == NULL)
     8        m_instance = new Singleton();
     9 return m_instance;
    10 }

    在第一次调用getInstance()的时候实例化,不调用就不会生成对象,不占据内存。适合在访问量较小时,以时间换空间。

     懒汉式是线程不安全的,比如两个线程同时运行到上面的第7行后,那么两个将分别创建两个实例,不满足单例的要求。

    为解决线程不安全,有了下面的加锁处理:

    1 // 懒汉式  加锁(代价高)
    2 Singleton* Singleton::getInstance()
    3 {
    4     mu.lock();
    5     if (m_instance == NULL)
    6         m_instance = new Singleton();
    7     mu.unlock();
    8     return m_instance;
    9 }

    但因为每次调用该方法时都要进行加锁,代价太高,因而出现了著名的双检查锁:

     1 //   懒汉式 双检查锁
     2 Singleton* Singleton::getInstance()
     3 {
     4     if (m_instance == NULL)    
     5     {
     6         mu.lock();
     7         if (m_instance == NULL)
     8             m_instance = new Singleton();
     9         mu.unlock();
    10     }
    11     return m_instance;
    12 }

    近乎完美。

    但,由于编译器等进行优化,导致内存读写存在reorder,有时导致双检查锁的不安全性,处理办法:

     1 //C++ 11版本之后的跨平台实现 
     2 // atomic c++11中提供的原子操作
     3 #include <atomic>
     4 #include <mutex>
     5    
     6 class Singleton {
     7 public:
     8        // 静态方法,供全局调用
     9        static Singleton* getInstance();
    10        void GetName() { cout << "我是单例" << endl; }
    11 private:
    12        //私有构造函数,不允许使用者自己生成对象
    13        Singleton() { cout << "Singleton被构造" << endl; }
    14        Singleton(const Singleton& other);
    15        static atomic<Singleton*> m_instance; //静态成员变量 
    16        static mutex m_mutex;
    17 };
    18 std::atomic<Singleton*> Singleton::m_instance;
    19 std::mutex Singleton::m_mutex;
    20 /*
    21 * std::atomic_thread_fence(std::memory_order_acquire);
    22 * std::atomic_thread_fence(std::memory_order_release);
    23 * 这两句话可以保证他们之间的语句不会发生乱序执行。
    24 */
    25 Singleton* Singleton::getInstance() {
    26        Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    27        std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    28        if (tmp == nullptr) {
    29            std::lock_guard<std::mutex> lock(m_mutex);
    30            tmp = m_instance.load(std::memory_order_relaxed);
    31            if (tmp == nullptr) {
    32                tmp = new Singleton;
    33                std::atomic_thread_fence(std::memory_order_release);//释放内存fence
    34                m_instance.store(tmp, std::memory_order_relaxed);
    35            }
    36        }
    37        return tmp;
    38 }

    最后是main方法

     1 void test01()
     2 {
     3     Singleton *pSingleton = Singleton::getInstance();
     4     pSingleton->GetName();
     5     Singleton *pSingleton1 = Singleton::getInstance();
     6     pSingleton1->GetName();
     7 }
     8 
     9 void test02()
    10 {
    11     Singleton *pSingleton = Singleton::getInstance();
    12     pSingleton->GetName();
    13 }
    14 
    15 
    16   // 多线程调用
    17 void thread01()
    18 {
    19     for (int i = 0; i < 5; i++)
    20     {
    21         //cout << "--thread01 working...." << endl;
    22         Singleton *lazy1 = Singleton::getInstance();
    23         //cout << "--thread01创建单例lazy1地址:" << lazy1 << endl;
    24     }
    25 }
    26 void thread02()
    27 {
    28     for (int i = 0; i < 5; i++)
    29     {
    30         //cout << "--thread02 working...." << endl;
    31         Singleton *lazy2 = Singleton::getInstance();
    32         //cout << "--thread02创建单例lazy2地址:" << lazy2 << endl;
    33     }
    34 }
    35 
    36 int main()
    37 {
    38 //     test01();
    39 //     test02();
    40 
    41     thread thread1(thread01);
    42     thread thread2(thread01);
    43     thread1.detach();
    44     thread2.detach();
    45     for (int i = 0; i < 5; i++)
    46     {
    47         //cout << "--Main thread working..." << endl;
    48         Singleton *main = Singleton::getInstance();
    49         //cout << "--Main 创建单例lazy地址:" << main << endl;
    50     }
    51 
    52     system("pause");
    53     return 0;
    54 }

    优点: 
    在系统内存中只存在一个对象,因此可以节约系统的资源,对于一些需要频繁创建和销毁的对象,使用单例模式无疑是提高了系统的性能;
    单例模式允许可变数目的实例。基于单例模式可以进行扩展,使用与控制单例对象相似的方法获得指定个数的实例对象,既节约了系统资源,又解决了由于单例对象共享过多有损性能的问题(自行提供指定数目实例对象的类可成为多例类) 。
    缺点:
    由于单例模式没有抽象层,所以扩展起来很难;
    单例类职责过重,在一定程度上违背了单一职责原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起;
    不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
    多线程模式下较复杂。

  • 相关阅读:
    web移动端开发经验总结
    《前端JavaScript面试技巧》笔记一
    《SEO在网页制作中的应用》视频笔记
    web前端开发笔记(2)
    OAuth2.0理解和用法
    基于hdfs文件创建hive表
    kafka 配置事项
    centos7时间同步
    lambda架构
    hbase hadoop版本
  • 原文地址:https://www.cnblogs.com/zhaoliankun/p/12630861.html
Copyright © 2020-2023  润新知