// C++11跨平台实现线程安全的DCL(Double Check Lock)单例,类似Java的volatile实现内存屏障,阻止CPU指令重排序 class Single { Single(){} Single(const Single& other) = delete; Single& operator=(const Single& other) = delete; static std::atomic<Single*> m_instance; static std::mutex m_mutex; public: static Single* GetInstance() { Single* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); // 获取内存fence if (tmp == nullptr) { std::lock_guard<std::mutex> locker(m_mutex); // 加锁, RAII tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Single; std::atomic_thread_fence(std::memory_order_release); // 释放内存fence m_instance.store(tmp, std::memory_order_relaxed); } }// 结束后,锁自动释放 return tmp; } }; std::mutex Single::m_mutex; std::atomic<Single*> Single::m_instance;
// 局部static变量实现泛型单例管理类,C++能够保证static变量只会被创建一次 template<typename T> class Singleton { Singleton() = delete; Singleton(const Singleton& other) = delete; Singleton& operator=(const Singleton& other) = delete; public: inline static T& GetInstance() { static T instance;// T必须能够执行默认初始化或提供了默认构造函数 return instance; } }; // 外部使用方法,只需要对外暴露Singleton<ClassName>就能实现对ClassName的单例管理
// 简单饿汉式,也可以使用局部static变量,这样就能等到调用GetInstance()函数时才会真正创建单例对象 class Singleton { Singleton() {} Singleton(const Singleton& other) = delete; Singleton& operator=(const Singleton& other) = delete; static Singleton m_instance; public: inline static Singleton* GetInstance() { return &m_instance; } }; // 真正定义,分配内存的地方 Singleton Singleton::m_instance;
C++中volatile关键字和Java的volatile关键字实现的功能不一样:
C++中volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。
Java中volatile关键字有2个作用:1、保证线程之间变量内存的可见性;2、阻止CPU指令重排序