【多线程】线程安全的单例模式

2024-07-19 1819阅读

文章目录

  • 什么是单例模式
    • 饿汉实现方式
      • 饿汉实现模式的特点
      • 懒汉实现方式
      • 懒汉实现方式的特点

        什么是单例模式

        单例模式是一个设计模式,其目的是确保一个类只有一个实例,并提供一个全局的访问点来访问该实例。单例模式常用于需要控制资源数量的场景,比如数据库连接池、日志管理器等。下面介绍常见的单例模式的实现方式。

        【多线程】线程安全的单例模式
        (图片来源网络,侵删)

        饿汉实现方式

        观察下面代码:

        class EagerSingleton {
        private:
            // 私有构造函数,防止外部实例化
            EagerSingleton() {}
            // 禁用拷贝构造函数和赋值运算符
            EagerSingleton(const EagerSingleton&) = delete;
            EagerSingleton& operator=(const EagerSingleton&) = delete;
            // 静态实例,类加载时初始化
            static EagerSingleton instance;
        public:
            // 提供全局访问点
            static EagerSingleton& getInstance() {
                return instance;
            }
        };
        // 初始化静态实例
        EagerSingleton EagerSingleton::instance;
        

        要想实现单例,从两个方面入手:1.如何实现禁止构造2个或者2个以上的实例化对象。2.如何实现只构造一个对象。

        对于前者,我们可以将构造函数私有化,这样就不能在类外部构造对象了。那怎么保证拥有一个实例化对象呢?这一点可以在类中使用static修饰一个该类的实例对象,这样就能在程序加载的时候自动创建一个实例化对象,由于static成员属于类本身的特性,所以能使用私有的构造函数,且只会加载一次。

        要想使用这个单例,就需要一个static修饰的成员方法专门用来提供全局的访问点,因为static成员函数可以直接通过类名使用,不需要创建实例化对象。 此外静态成员变量需要在类外部初始化。

        饿汉实现模式的特点

        • 类加载的时候实例化
        • 私有化构造函数
        • 线程安全:由于实例化是在类加载的时候完成的,且类加载的过程中由C++标准保证了线程安全,因此不需要额外的同步机制来保证线程安全。

          懒汉实现方式

          观察下面代码

          class LazySingleton {
          private:
              // 私有的静态指针变量,用于指向唯一的实例
              static LazySingleton* instance;
              // 私有构造函数,防止外部实例化
              LazySingleton() {}
          public:
              // 提供全局访问点
              static LazySingleton* getInstance() {
                  if (instance == nullptr) {
                      instance = new LazySingleton();
                  }
                  return instance;
              }
          };
          // 初始化静态指针变量
          LazySingleton* LazySingleton::instance = nullptr;
          

          注意其中和饿汉实现方式的区别,懒汉实现单例模式中static修饰的是一个实例对象的指针。这样一来,类加载的时候就不会构造自动一个实例化对象。具体的观察这一段代码:

          static LazySingleton* getInstance() {
                  if (instance == nullptr) {
                      instance = new LazySingleton();
                  }
                  return instance;
              }
          

          这一段代码保证了该类只能实例化对象一次,且只在第一次调用getInstance() 函数时才会创建。这样一来也能实现单例模式。但是和饿汉模式不一样的是,这种单例模式只在第一次使用的时候实例化一个对象,而饿汉模式是程序开始时不管你用不用都会实例化一个。这种机制又被称为延迟实例化机制。此外,这种机制在多线程模式下是不安全的。比如可能会有多个线程

          new LazySingleton();

          为了保证线程安全,我们需要使用锁来保证创建实例化对象时同步。例如下面代码:

          #include 
          class LazySingleton {
          private:
              static LazySingleton* instance;
              static std::mutex mtx;
              LazySingleton() {}
          public:
              static LazySingleton* getInstance() {
                  std::lock_guard lock(mtx);
                  if (instance == nullptr) {
                      instance = new LazySingleton();
                  }
                  return instance;
              }
          };
          // 初始化静态变量
          LazySingleton* LazySingleton::instance = nullptr;
          std::mutex LazySingleton::mtx;
          

          上面加锁方式在每次调用getInstance()这个函数的时候都会加一次锁,频繁的调用该函数可能会造成一定的性能问题。

          对于上面加锁方式还可以进一步进行优化,例如采用双重检查锁定,具体优化的代码如下:

          #include 
          class LazySingleton {
          private:
              static LazySingleton* instance;
              static std::mutex mtx;
              LazySingleton() {}
          public:
              static LazySingleton* getInstance() {
                  if (instance == nullptr) {
                      std::lock_guard lock(mtx);
                      if (instance == nullptr) {
                          instance = new LazySingleton();
                      }
                  }
                  return instance;
              }
          };
          // 初始化静态变量
          LazySingleton* LazySingleton::instance = nullptr;
          std::mutex LazySingleton::mtx;
          

          这样一来,只有在首次创建对象时会上锁,其余时候都不会也不需要。

          懒汉实现方式的特点

          • 线程不安全
          • 延迟实例化
          • 需要使用互斥锁保证线程安全

            具体原因分析上面已经阐述过了,这里就不做过多分析了。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]