单例模式
我们知道,设计模式有着深邃的思想。现在来看看单例模式。
它的核心思想在于:有且仅有一个实例对象。
使用场景:某些类对象占用资源很大需要长时间驻留内存,多个模块共享的资源,需要频繁实例化然后访问的对象。比如:线程池。
实现方式:饿汉式(预先实例化,空间换时间),懒汉式(有请求时才实例化,时间换空间)
线程安全问题:饿汉式,由于预先创建了对象,所以就算多线程访问,也不会发生共享对象不同的情况;而懒汉式,存在实例化的过程,该过程如果不加锁,就会造成一个线程正在实例化对象(还未完成),然而另一个线程开始实例化的尴尬境地,造成实例化了多个对象的后果,违反了“只有一个实力”初衷。
看看如何实现👇
1. 饿汉式:实现简单,线程安全,无需加锁保证线程安全。
1 #include "pch.h" 2 #include <iostream> 3 using namespace std; 4 5 //单例模式:饿汉式 6 //1.实现单例,实例化对象 7 //2.不存在线程安全问题 8 class Singleton 9 { 10 public: 11 ~Singleton(); 12 static Singleton * getInstance(); 13 static void destroy(); 14 private: 15 Singleton();//1.私有构造函数 16 static Singleton* instance;//2.生成静态对象 17 Singleton(const Singleton &s) = delete;//禁止实现拷贝构造 18 Singleton* operator=(const Singleton &s) = delete;//禁止实现拷贝赋值 19 }; 20 21 Singleton* Singleton::instance = new Singleton();//静态对象实例化 22 Singleton* Singleton::getInstance()//3.提供全局访问点 23 { 24 return instance; 25 } 26 void Singleton::destroy() 27 { 28 if (instance != nullptr) 29 { 30 delete instance; 31 instance = nullptr; 32 cout << "实例被析构" << endl; 33 } 34 else 35 cout << "实例不存在" << endl; 36 } 37 38 Singleton::Singleton() 39 { 40 } 41 42 Singleton::~Singleton() 43 { 44 } 45 46 int main() 47 { 48 Singleton *s1 = Singleton::getInstance(); 49 Singleton *s2 = Singleton::getInstance(); 50 if (s1 == s2) 51 cout << "相同实例" << endl; 52 s1->destroy(); 53 s2->destroy(); 54 return 0; 55 }
2. 懒汉式:需要时才实例化对象,存在线程安全问题,需要互斥锁,进而需要双重判断,提高效率。
初始版本:
1 #include <iostream> 2 using namespace std; 3 4 //单例模式:饿汉式 5 //1.实现单例,实例化对象 6 //2.不存在线程安全问题 7 class Singleton 8 { 9 public: 10 ~Singleton(); 11 static Singleton * getInstance(); 12 static void destroy(); 13 private: 14 Singleton();//1.私有构造函数 15 static Singleton* instance;//2.生成静态对象,先不实例化 16 Singleton(const Singleton &s) = delete;//禁止实现拷贝构造 17 Singleton* operator=(const Singleton &s) = delete;//禁止实现拷贝赋值 18 }; 19 20 Singleton* Singleton::instance = nullptr; 21 22 Singleton* Singleton::getInstance()//3.提供全局访问点 23 { 24 if (instance == nullptr)//还未实例化 25 { 26 instance = new Singleton();//进行实例化 27 return instance; 28 } 29 return instance;//已经实例化返回存在的实例 30 } 31 void Singleton::destroy() 32 { 33 if (instance != nullptr) 34 { 35 delete instance; 36 instance = nullptr; 37 cout << "实例被析构" << endl; 38 } 39 else 40 cout << "实例不存在" << endl; 41 } 42 43 Singleton::Singleton() 44 { 45 cout << "实例化" << endl; 46 } 47 48 Singleton::~Singleton() 49 { 50 } 51 52 int main() 53 { 54 Singleton *s1 = Singleton::getInstance(); 55 Singleton *s2 = Singleton::getInstance(); 56 if (s1 == s2) 57 cout << "相同实例" << endl; 58 s1->destroy(); 59 s2->destroy(); 60 return 0; 61 }
(single1 和 single2 指向的是相同的实例instance,所以只会构造一次,地址相同;
上面👆的写法在多线程下并不安全,当两个线程同时运行到instance==nullptr时, 就会产生两个实例,这样就破坏了单例的原则---只有一个实例!
安全的单例模式
【1】首先我们会想到互斥锁,让关键代码成为临界区。👇
1 Singleton* Singleton::getIntance() 2 { 3 mutex.lock(); //std::mutex 4 if (instance == nullptr) 5 instance = new Singleton(); 6 mutex.unlock(); 7 return instance; 8 }
【此种做法,在VS2017上调试,程序会因为:mutex destroyed while busy终止,通过下面的双重检查可以解决该问题】
这样做可以是的线程同步,但效率太低,我们试想,所有的线程都要等待解锁,其实只要第一个进入后对象产生, 其他的就可以直接返回instance了,不必要同步的等待开锁,浪费了时间。
怎么提高效率呢?👇
【2】double check双重检查
1 Singleton* Singleton::getIntance() 2 { 3 if (instance == nullptr)//check1 4 { 5 imutex.lock(); 6 if (instance == nullptr)//check2 7 instance = new Singleton(); 8 imutex.unlock(); 9 } 10 11 return instance; 12 }
【源码】
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "pch.h" 2 #include <iostream> 3 #include <mutex> 4 using namespace std; 5 6 //单例模式:饿汉式 7 //1.实现单例,实例化对象 8 //2.不存在线程安全问题 9 static mutex imutex; 10 class Singleton 11 { 12 public: 13 ~Singleton(); 14 static Singleton * getInstance(); 15 static void destroy(); 16 private: 17 Singleton();//1.私有构造函数 18 static Singleton* instance;//2.生成静态对象,先不实例化 19 Singleton(const Singleton &s) = delete;//禁止实现拷贝构造 20 Singleton* operator=(const Singleton &s) = delete;//禁止实现拷贝赋值 21 }; 22 23 Singleton* Singleton::instance = nullptr; 24 25 Singleton* Singleton::getInstance()//3.提供全局访问点 26 { 27 if (instance == nullptr)// 28 { 29 imutex.lock();//上锁 30 if (instance == nullptr)//还未实例化 31 { 32 instance = new Singleton();//进行实例化 33 imutex.unlock();//解锁 34 } 35 } 36 return instance;//已经实例化返回存在的实例 37 } 38 void Singleton::destroy() 39 { 40 if (instance != nullptr) 41 { 42 delete instance; 43 instance = nullptr; 44 cout << "实例被析构" << endl; 45 } 46 else 47 cout << "实例不存在" << endl; 48 } 49 50 Singleton::Singleton() 51 { 52 cout << "实例化" << endl; 53 } 54 55 Singleton::~Singleton() 56 { 57 } 58 59 int main() 60 { 61 Singleton *s1 = Singleton::getInstance(); 62 Singleton *s2 = Singleton::getInstance(); 63 if (s1 == s2) 64 cout << "相同实例" << endl; 65 s1->destroy(); 66 s2->destroy(); 67 return 0; 68 }
从【1】我们可以看出,只要第一个线程进入临界区,其他的不需要进入,只要拿到实例返回就可以了,所以我们进行double check,第一个线程进来时为nullptr,进入实例化了instance;其他的线程运行到
if (instance == nullptr)//check1
时,instance != nullptr, 直接就返回。这样一来,大大提高了并发效率。
甚至我们可以从Tomcat Servlet 编译jsp生成的Java文件中见到它的身影!
图看不清,看源码吧!
1 public javax.el.ExpressionFactory _jsp_getExpressionFactory() { 2 if (_el_expressionfactory == null) { 3 synchronized (this) { 4 if (_el_expressionfactory == null) { 5 _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); 6 } 7 } 8 } 9 return _el_expressionfactory; 10 } 11 12 public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { 13 if (_jsp_instancemanager == null) { 14 synchronized (this) { 15 if (_jsp_instancemanager == null) { 16 _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); 17 } 18 } 19 } 20 return _jsp_instancemanager; 21 }
总结:
饿汉式,先加载,安全不加锁;
懒汉式,后加载,互斥双检查。
( ̄_, ̄ )一点都不押韵!