单例模式

我们知道,设计模式有着深邃的思想。现在来看看单例模式。

它的核心思想在于:有且仅有一个实例对象。

使用场景:某些类对象占用资源很大需要长时间驻留内存,多个模块共享的资源,需要频繁实例化然后访问的对象。比如:线程池。

实现方式:饿汉式(预先实例化,空间换时间),懒汉式(有请求时才实例化,时间换空间)

线程安全问题:饿汉式,由于预先创建了对象,所以就算多线程访问,也不会发生共享对象不同的情况;而懒汉式,存在实例化的过程,该过程如果不加锁,就会造成一个线程正在实例化对象(还未完成),然而另一个线程开始实例化的尴尬境地,造成实例化了多个对象的后果,违反了“只有一个实力”初衷。


 

看看如何实现👇

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 }

【源码】

 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 }
View Code

从【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   }

总结:

饿汉式,先加载,安全不加锁;

懒汉式,后加载,互斥双检查。

( ̄_, ̄ )一点都不押韵!

Java选手,参考这里

posted @ 2019-03-24 11:12  yocichen  阅读(355)  评论(0编辑  收藏  举报