1.单例模式简述
单例模式是⼀种创建型设计模式, 它的核⼼思想是保证⼀个类只有⼀个实例,并提供⼀个全局访问点来访问这个实例。
2.单例模式的作用
单例模式提供了对唯一实例的受控访问,这样做可以避免多次创建相同对象,从而可以节省系统资源,而且多个模块可以通过单例实例共享数据。
3.单例模式与全局变量的区别
单例模式是一种设计模式,保证一个类仅有一个实例,并提供一个全局访问点。它适用于需要全局使用且仅需要一个实例的情况,比如日志系统、线程池等。单例模式可以确保对象的唯一性,避免了多次实例化造成的资源浪费,并且可以提供全局访问点方便地对实例进行操作。然而,单例模式的实现可能涉及多线程安全、资源释放等问题,需要仔细设计和考虑。
全局变量是在程序中定义的可以被任何函数访问的变量。全局变量的优点是方便在程序的任何地方访问,可以节省参数传递的开销,提高程序的可读性。然而,过多地使用全局变量可能导致代码耦合性增加,使得程序难以维护和扩展。此外,全局变量的存在也增加了程序的不确定性,可能导致代码出现意外的副作用。
总的来说,单例模式适用于需要确保唯一实例的场景,可以避免多次实例化以及提供全局访问点的便利性,但需要考虑多线程安全等问题;而全局变量虽然方便访问,但可能导致耦合性增加和程序设计的复杂性,需要慎重使用。在实际应用中,需要根据具体情况来选择使用单例模式还是全局变量,以便兼顾代码的简洁性和可维护性。------《来自chatGPT》
简单来说,全局变量访问方便,但会紧耦合,另外全局变量一开始就会初始化,做不到懒加载(需要使用时再实例化,懒汉单例可以做到)。而单例需要考虑线程安全。
3.单例模式的实现要求
- 私有的构造函数:防⽌外部代码直接创建类的实例;
- 私有的静态实例变量:保存该类的唯⼀实例;
- 公有的静态⽅法:通过公有的静态⽅法来获取类的实例;
考虑到线程安全和编译优化,情况可能会更加复杂。
4.懒汉单例的C++单线程版本
class Singleton{
private:
static Singleton* m_instance;
Singleton();
public:
static Singleton* getInstance();
}
Singleton* Singleton::m_instance=nullptr;
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){ //多线程情况下不安全,假设线程A执行完这一行,但下一行还没执行,而恰好换线程B执行,那么A和B会同时进入下一行,那么会不止创建一个实例。
m_instance = new Singleton();
}
return m_instance;
}
5.懒汉单例的C++多线程版本
5.1保证线程安全的加锁版本
...
Singleton* Singleton::getInstance(){
Lock lock; //lock是局部变量,函数结束会自动调用类的析构函数
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}
锁的代价太高,getInstance本质上是读操作,读操作不应该是互斥的,也就是说m_instance一旦不为空,就应该允许多线程获取它。
5.2双检查锁版本
Singleton* Singleton::getInstance(){
if(m_instance==nullptr){ //先检查再上锁
Lock lock;
if(m_instance == nullptr){
m_instance = new Singleton();
}
}
return m_instance;
}
逻辑上,双检查锁是没问题的,一旦m_instance不为空,多线程都可以访问它,但可惜的是编译器优化会reorder,也就是汇编代码的顺序和高级语言代码顺序不是完全一致的,比如,new的过程会先分配内存,再执行类的构造函数,假设在分配完内存后,进行了线程切换,此时m_instance不是nullptr,但m_instance代表的对象可能还没有构造好就被使用了。因此双检查锁也还不是线程安全的,于是有了下面的最终版。
5.2最终版(C++11版本之后才有的跨平台实现)
std::atomic<Singleton*> Singlton::m_instance; //声明一个原子对象
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance(){
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::automic_thread_fence(std::memory_order_acquire); //获取内存fence,屏蔽编译器的reorder, tmp就不会被reorder了
if(tmp == nullptr){
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instace.load(std::memory_order_relaxed);
if(tmp == nullptr){
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release); //释放内存fence
m_instace.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
注解:
automic_thread_fence的介绍,在其它语言中,如Java中不用这么复杂,给m_instance加个volatile关键字就行了,VC下也可以,但不是跨平台的。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 私有构造⽅法,防⽌外部实例化
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}