单例模式

单例模式:

在实现单例模式之前需要先了解一下静态(全局/局部)变量的初始化方式:

全局变量、non-local static 变量(文件域的静态变量和类的静态成员变量)在main执行之前就已分配内存并初始化;local static 变量(局部静态变量)同样是在 main 前就已分配内存,第一次使用时初始化

非局部静态变量一般在 main 执行之前的静态初始化过程中分配内存并初始化,可以认为是线程安全的

C++11 保证静态局部变量的初始化过程是线程安全的

 

设计模式经典GoF定义的单例模式需要满足以下两个条件:

1)保证一个类只创建一个实例
2)提供对该实例的全局访问点

 

在设计单例模式时有亮点需要注意:

单例模式的实现分懒汉模式和饿汉模式以及是否线程安全

 

懒汉模式和饿汉模式的区别:

懒汉模式中,直到 Instance() 被访问,才会生成实例,这种特性被称为延迟初始化(Lazy Initialization),这在一些初始化时消耗较大的情况有很大优势。 

饿汉模式中,在编译器初始化的时候就完成了实例的创建,和上述的 Lazy Singleton 相反

 

实现1:

懒汉模式,非线程安全,堆内存

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 class Singelton {
 6 private:
 7     static Singelton *singel;
 8     //将默认构造函数、析构函数定义为私有的
 9     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
10     Singelton(){}
11     Singelton(const Singelton&) = delete;
12     Singelton& operator=(const Singelton&) = delete;
13     ~Singelton(){}//将析构函数声明成private,防止被意外delete
14 
15 public:
16     static Singelton* GetInstance() {
17         //非线程安全
18         //如果有多个线程同时进行singel == nullptr判断,则会创建多个实例
19         if(singel == nullptr) singel = new Singelton();
20         return singel;
21     }
22 };
23 
24 //类静态成员需要类外初始化
25 Singelton* Singelton::singel = nullptr;
26 
27 int main(void) {
28     // Singelton cnt;//error,访问私有的默认构造函数
29 
30     Singelton *s1 = Singelton::GetInstance();
31     Singelton *s2 = Singelton::GetInstance();
32 
33     cout << s1 << endl;
34     cout << s2 << endl;
35 
36 // 输出:
37 // 0x28053f8
38 // 0x28053f8
39 
40     return 0;
41 }
View Code

第一次访问 GetInstance 时才创建实例,显然是懒汉模式。如果有多个线程同时进行 singel == nullptr 判断,则会创建多个实例,所以其是非线程安全的。并且该实现中一旦创建了实例,是不可删除的

还有一个问题是 GetInstance 为啥是 static 的:如果 GetInstance 被定义为非静态函数的话,要访问 GetInstance 则必须先创建一个实例,而要在访问 GetInstance 之前创建实例则说明该类可以创建多个实例,这显然不符合单例模式的条件

 

针对实现1中的非线程安全问题,我们可以给 if 语句加个锁

实现2:

懒汉模式,线程安全,堆内存

 1 #include <iostream>
 2 #include <vector>
 3 #include <mutex>
 4 using namespace std;
 5 
 6 class Singelton {
 7 private:
 8     static std::mutex mtex;//静态锁
 9     static Singelton *singel;
10     //将默认构造函数、析构函数定义为私有的
11     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
12     Singelton(){}
13     Singelton(const Singelton&) = delete;
14     Singelton& operator=(const Singelton&) = delete;
15     ~Singelton(){}//将析构函数声明成private,防止被意外delete
16 
17 public:
18     static Singelton* GetInstance() {//注意,在静态成员函数中中只能直接使用静态数据成员
19         mtex.lock();
20         if(singel == nullptr) singel = new Singelton();
21         mtex.unlock();
22 
23         return singel;
24     }
25 };
26 
27 //类静态成员需要类外初始化
28 Singelton* Singelton::singel = nullptr;
29 std::mutex Singelton::mtex;
30 
31 int main(void) {
32     // Singelton cnt;//error,访问私有的默认构造函数
33 
34     Singelton *s1 = Singelton::GetInstance();
35     Singelton *s2 = Singelton::GetInstance();
36 
37     cout << s1 << endl;
38     cout << s2 << endl;
39 
40 // 输出:
41 // 0x28053f8
42 // 0x28053f8
43 
44     return 0;
45 }
View Code

注意:加锁是比较费时的,而按照上面的实现,每一次访问 Getlnstance 函数都要加/解锁一次,而实际上我们只需要在创建第一个实例时加锁。显然我们可以在加锁之前先判断一下是否已经创建了实例,从而避免已经创建了实例的情况下加/解锁。即双检测锁模式

 

据上面的分析有

实现3:

懒汉模式,线程安全,堆内存

 1 #include <iostream>
 2 #include <vector>
 3 #include <mutex>
 4 using namespace std;
 5 
 6 class Singelton {
 7 private:
 8     static std::mutex mtex;//静态锁
 9     static Singelton *singel;
10     //将默认构造函数、析构函数定义为私有的
11     //其它拷贝控制函数定义为删除的(因为这个代码中我们只需要执行默认构造即可)
12     Singelton(){}
13     Singelton(const Singelton&) = delete;
14     Singelton& operator=(const Singelton&) = delete;
15     ~Singelton(){}//将析构函数声明成private,防止被意外delete
16 
17 public:
18     static Singelton* GetInstance() {//注意,在静态成员函数中中只能直接使用静态数据成员
19         //双检测锁模式
20         if(singel == nullptr){
21             mtex.lock();
22             if(singel == nullptr) singel = new Singelton();
23             mtex.unlock();
24         }
25         return singel;
26     }
27 };
28 
29 //类静态成员需要类外初始化
30 Singelton* Singelton::singel = nullptr;
31 std::mutex Singelton::mtex;
32 
33 int main(void) {
34     // Singelton cnt;//error,访问私有的默认构造函数
35 
36     Singelton *s1 = Singelton::GetInstance();
37     Singelton *s2 = Singelton::GetInstance();
38 
39     cout << s1 << endl;
40     cout << s2 << endl;
41 
42 // 输出:
43 // 0x28053f8
44 // 0x28053f8
45 
46     return 0;
47 }
View Code

 

由于 C++11 保证静态局部变量的初始化过程是线程安全的特性,我们也可以通过静态局部变量来实现线程安全

实现4:

懒汉模式,线程安全,自由存储区

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 class Singelton {
 6 private:
 7     Singelton(){}//将默认构造函数定义为私有的
 8     Singelton(const Singelton&) = delete;
 9     Singelton& operator=(const Singelton&) = delete;
10     ~Singelton(){}
11 
12 public:
13     static Singelton* GetInstance() {
14         //c++11开始,局部静态变量的初始化过程是线程安全的
15         static Singelton instance;//静态变量的生命周期是全局的,所以可以返回局部静态变量的引用
16         return &instance;
17     }
18 };
19 
20 int main(void) {
21     // Singelton cnt;//error,访问私有的默认构造函数
22 
23     Singelton *s1 = Singelton::GetInstance();
24     Singelton *s2 = Singelton::GetInstance();
25 
26     cout << s1 << endl;
27     cout << s2 << endl;
28 
29 // 输出:
30 // 0x2b853d8
31 // 0x2b853d8
32 
33     return 0;
34 }
View Code

 

实现5:

饿汉模式,线程安全,自由存储区

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 class Singelton {
 6 private:
 7     static Singelton instance;
 8     Singelton(){}//将默认构造函数定义为私有的
 9     Singelton(const Singelton&) = delete;
10     Singelton& operator=(const Singelton&) = delete;
11     ~Singelton(){}
12 
13 public:
14     static Singelton* GetInstance() {
15         return &instance;
16     }
17 };
18 
19 //类外声明类静态数据成员
20 Singelton Singelton::instance;//在mian之前初始化
21 
22 int main(void) {
23     // Singelton cnt;//error,访问私有的默认构造函数
24 
25     Singelton *s1 = Singelton::GetInstance();
26     Singelton *s2 = Singelton::GetInstance();
27 
28     cout << s1 << endl;
29     cout << s2 << endl;
30 
31 // 输出:
32 // 0x489008
33 // 0x489008
34 
35     return 0;
36 }
View Code

注意:饿汉模式是在 main 之前初始化的,自动满足线程安全

 

 

posted @ 2018-03-24 19:50  geloutingyu  阅读(192)  评论(0编辑  收藏  举报