#include <thread>
#include <iostream>
#include <mutex>
// 最原始的单例模式的写法,不是线程安全的,并且会内存泄漏。
// 线程不安全的原因:假设有两个线程都执行getInstance函数。当线程1调用singleton = new Singleton1()
// 语句时候,操作系统系统突然切换到线程2,线程2判断if (singleton == nullptr)生效,线程2执行
// singleton = new Singleton1();当线程2执行完后,singleton已经生成。然后切换到线程1,线程1继续执行
// singleton = new Singleton1(),singleton会再次生成。这不符合单例设计的原则。
// 内存泄漏的原因:析构函数没法调用,所以无法通过析构函数调用delete,删除singleton内存class Singleton1
{public:~Singleton1() {std::cout << "Singleton1析构函数调用" << std::endl;} // 析构函数其实不会调用,所以new出来的静态成员变量会内存泄漏。static Singleton1* getInstance(){if (singleton == nullptr){singleton = new Singleton1();}return singleton;}void func(){printf("调用func函数\n");}
private:// static函数只能调用静态成员变量或者静态函数,所以下面这个静态成员变量必须为static static Singleton1* singleton;Singleton1(){}
};
// 静态非const整形成员变量必须在类外定义
Singleton1* Singleton1::singleton = nullptr;// 再写个单例模式,采用类中类解决内存泄露的问题。其实在main函数中也可以手动delete,只不过不是很优雅。
class Singleton2
{private:Singleton2(){}static Singleton2* singleton;
public:~Singleton2() {std::cout << "Singleton2析构函数调用" << std::endl;} // 析构函数其实不会调用,所以new出来的静态成员变量会内存泄漏。static Singleton2* getInstance(){if (singleton == nullptr){singleton = new Singleton2();static PtrCycle ptr; // C++能确保静态变量只能被初始化一次,不会因为调用getInstance,多次创建静态对象。}return singleton;}void func(){printf("调用func函数\n");}private:class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //这里会调用析构函数singleton = nullptr;std::cout << "释放内存" << std::endl;}}};
};
Singleton2* Singleton2::singleton = nullptr; //必须要在类外初始化// 上面的写法还是线程不安全的。为了解决线程安全,引申出下面的饿汉式和懒汉式写法。
// 饿汉式:一开始就初始化单例对象
// 饿汉式写法一:把对象用new放在堆上。
class Singleton3
{private:static Singleton3* singleton;Singleton3(){}
public:void func(){printf("调用func函数\n");}static Singleton3* getInstance(){static PtrCycle ptr;return singleton;}~Singleton3(){std::cout << "Singleton3析构函数" << std::endl;}
private:class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; // 这里会调用析构函数singleton = nullptr;std::cout << "释放内存" << std::endl;}}};
};
Singleton3* Singleton3::singleton = new Singleton3(); //静态对象类外初始化,其实这个写法不好
// 上面new出来的这个指针,如果getInstace函数从没被调用过,那么因为new Singleton3()
// 得到的内存从没被释放,会发生内存泄漏。// 饿汉式写法二:把对象放在静态区,不使用new。
// 这种写法不需要写类中类去释放内存,或者在main函数中手动删除内存
class Singleton4
{private:static Singleton4 singleton;Singleton4(){}
public:void func(){printf("调用func函数\n");}~Singleton4(){std::cout << "Singleton4析构函数" << std::endl;}static Singleton4* getInstance(){return &singleton;}
};
Singleton4 Singleton4::singleton; // 静态对象类外初始化
// 饿汉式的总结
// 由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,可确保单例对象的唯一性。线程是安全的。
// 缺点:无论系统运行时是否需要使用该单例对象,都会在类加载时创建对象,资源利用效率不高。// 懒汉式:需要时候再实例化单例对象。
// 懒汉式1:直接加个锁。
// 这样的代码其实有个很严重的问题,就是代码中可能需要频繁调用getInstance这个函数
// 因为只有借助getInstace这个函数才能获取到单例类对象,然后才能调用单例类的其他成员
// 函数。为了解决一个初始化该类对象的互斥问题,居然在getInstace里面加了互斥量。导致
// 所有时刻,调用getInstance这个函数,都会因为锁互斥一下,严重影响性能。因为除了初始化时刻,其他
// 时候完全不需要互斥。一旦初始化完成,if (singleton == nullptr)永远不会成立,所以singleton = new Singleton()
// 永远不会再次执行。
class Singleton5
{private:static Singleton5* singleton;static std::mutex my_mutex; //这里用的静态成员变量,保证所有用到这个类的,用的是同一个互斥量。当然定义一个全局互斥量也可以。Singleton5(){}class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //这里会调用析构函数singleton = nullptr;std::cout << "释放内存" << std::endl;}}};public:~Singleton5(){std::cout << "Singleton5析构函数执行" << std::endl;}static Singleton5* getInstance(){std::lock_guard<std::mutex> my_guard(my_mutex);if (singleton == nullptr){singleton = new Singleton5();static PtrCycle ptr;}return singleton;}void func(){printf("调用func函数\n");}};
std::mutex Singleton5::my_mutex;
Singleton5* Singleton5::singleton = nullptr;// 懒汉式2:双重锁定
// 双重锁定的写法,保证线程1在if (singleton == nullptr)成立之后,
// singleton = new Singleton6();运行之前,一定不会发生上下文的切换。
// 因此会创建完成单例类对象。然后互斥量解锁之后,哪怕发生上下文切换,换到了另一个
// 线程,此时if (singleton == nullptr)一定不会成立,因此不会再调用第二次 singleton = new Singleton6()。
// 初始化时候,需要用到这个互斥量加锁,其他时候并不会用到这个互斥量。因为一旦初始化完成之后
// if (singleton == nullptr)一定不会成立,因此不会因为调用一次getInstance就创建一次互斥量。
// 因此大大提升了代码的运行效率。class Singleton6
{private:static Singleton6* singleton;static std::mutex my_mutex;Singleton6(){}class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //这里会调用析构函数singleton = nullptr;std::cout << "释放内存" << std::endl;}}};public:~Singleton6(){std::cout << "Singleton6析构函数执行" << std::endl;}static Singleton6* getInstance(){if (singleton == nullptr){std::lock_guard<std::mutex> my_guard(my_mutex);if (singleton == nullptr){singleton = new Singleton6();static PtrCycle ptr;}}return singleton;}void func(){printf("调用func函数\n");}};
std::mutex Singleton6::my_mutex;
Singleton6* Singleton6::singleton = nullptr;// C++11之后,静态局部对象是实现多线程安全的单例类最佳写法。
// C++11之后,多个线程同时初始化一个同一局部静态对象,可以保证只初始化一次。
//  在实现单例的过程中要注意如下问题:
// 1. 构造函数应该声明为非公有,从而禁止外界创建实例。
// 2. 拷贝操作和移动操作也应该禁止。
// 3. 只能通过 Singleton 的公有特定类操作访问它的唯一实例(C++中的一个公有静态成员函数)
class Singleton7
{public:~Singleton7(){std::cout << "Singleton7析构函数执行" << std::endl;}static Singleton7* getInstance(){static Singleton7 singleton_tmp;return &singleton_tmp;}void func(){printf("调用func函数\n");}private:Singleton7(){}// 拷贝构造函数Singleton7(const Singleton7& singleton) = delete;// 拷贝赋值函数Singleton7& operator = (const Singleton7& singleton) = delete;// 移动构造函数Singleton7(Singleton7&& singleton) = delete;// 移动赋值构造函数Singleton7& operator = (Singleton7&& singleton) = delete;
};void my_thread()
{printf("thread run\n");Singleton3* s = Singleton3::getInstance();printf("address is: %p \n", s);s->func();
}int main()
{std::thread my_thread1(my_thread);std::thread my_thread2(my_thread);my_thread1.join();my_thread2.join();return 0;
}

不过有一点需要说明的是:将单例类放在主线程中,在其他子线程创建并运行之前,将单例类初始化完成是强烈推荐的。这样就不存在多个子线程对这个单例类对象访问的冲突问题,因为一旦初始化完成,再次调用getInstance的操作全是读操作,是线程安全的。
如果你需要在自己创建的子线程中创建单例类对象,为了保证多线程安全,可以参考我的代码写法。

C++线程安全单例类最全总结相关推荐

  1. C++ 九阴真经之线程安全单例类

    C++ 九阴真经之线程安全单例类 与之前的单例类似,但普通的单例类是非线程安全的,就是是你不能有些线程读,有些线程写,一般来说,要安全访问单例,就需要用户自己加载来控制对单例的访问. 日常开发中经常会 ...

  2. 【线程安全】—— 单例类双重检查加锁(double-checked locking)

    1. 三个版本单例类的实现 版本1:经典版 public class Singleton {public static Singleton getInstance() {if (instance == ...

  3. 游戏设计模式——C++单例类

    前言: 本文将探讨单例类设计模式,单例类的懒汉模式/饿汉模式,单例类的多线程安全性,最后将利用C++模板减少单例类代码量. 本文假设有一个Manager管理类,并以此为探究单例类的设计模式. 懒汉模式 ...

  4. java-Transient关键字、Volatile关键字介绍和序列化、反序列化机制、单例类序列化

    - Transient关键字 Java的serialization提供了一种持久化对象实例的机制.当持久化对象时,可能有一个特殊的对象数据成员,我们不想  用serialization机制来保存它.为 ...

  5. (七)boost库之单例类

    一.boost.serialzation的单件实现 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问 ...

  6. c++实现单例类(懒汉与饿汉)

    教科书里的单例模式 我们都很清楚一个简单的单例模式该怎样去实现:构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实例,实例的 ...

  7. java中单例设计模式登记式单例类_java23种设计模式-创建型模式之单例模式

    单例模式(Singleton) 单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频 ...

  8. 设计模式详解:Singleton(单例类)

    Singleton(单例类) 设计模式学习:概述 意图 保证每一个类仅有一个实例,并为它提供一个全局访问点. 顾名思义,单例类Singleton保证了程序中同一时刻最多存在该类的一个对象. 有些时候, ...

  9. 单例模式及单例类的两种实现

    单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在 ...

  10. Android学习-Kotlin语言入门-变量、函数、语法糖、when、for-in、主构造函数、单例类、函数式API、集合遍历、隐式Intent、Activity生命周期、四种启动模式、标准函数

    探究java语言的运行机制 变量.函数.语法糖 when条件语句 for循环 主构造函数.次构造函数 数据类和单例类 集合的创建与遍历 集合的函数式API 创建菜单 隐式使用Intent Activi ...

最新文章

  1. 解决日常bug的正确姿势
  2. java 日志 异步_log4j 详解异步日志的配置和测试
  3. 目标检测——如何处理任意输入尺寸的图片
  4. JAVA入门[5]-初步搭建SpringMVC站点
  5. yii第三方插件snoopy配置
  6. 计算机音乐公子,抖音公子在等谁是什么梗 公子在等谁背景音乐《心机》
  7. libmudbus库使用的一二事
  8. Coursera 算法二 week 4 Boggle
  9. 4.1.2. Constants
  10. html添加一条虚线垂直的,【html问题】在网页中添加垂直分割线
  11. Unity3D游戏开发之RPG游戏剧情呈现策略
  12. color a dir/s_2级绘本(A)Lesson 25:Kipper扮演小丑
  13. 【电子器件笔记7】MOS管参数和选型
  14. 小学生买台灯用哪个灯光好点?盘点最好的学生护眼灯品牌排行
  15. 2023年HCIA-Cloud Service V3.0 H13-811(最新考试题库200题)
  16. console 小记
  17. Centos7 查看开机启动项命令
  18. 《流浪地球》里的引力弹弓人类真的实现过!张朝阳在线手推旅行者号木星之旅...
  19. Java 第 33 课 1282. 用户分组 523. 连续的子数组和
  20. 闲置小U盘变身最强大路由器

热门文章

  1. java实现视频在线播放并解决java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
  2. UFO报表转换不成功!请检查文件版本或使用DOS文件转换工具
  3. 如何将PNG图像转换为word文档?
  4. 2015年数模A题太阳影子定位学习笔记
  5. 一位教授跟我说:线性代数应该这样学
  6. Could not load NIB in bundle: 'NSBundle /Users/wyd/Library/Application Support/iPhone Simulator/5.0
  7. 粒子群优化算法matlab实现,粒子群优化算法的MATLAB程序实现+源程序
  8. Linux使文件变成二进制,linux 二进制文件显示方法
  9. jedate日期插件使用
  10. 三码合一方法 制作QQ、微信、支付宝收款码合一