单例模式
单例模式:
在实现单例模式之前需要先了解一下静态(全局/局部)变量的初始化方式:
全局变量、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 }
第一次访问 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 }
注意:加锁是比较费时的,而按照上面的实现,每一次访问 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 }
由于 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 }
实现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 }
注意:饿汉模式是在 main 之前初始化的,自动满足线程安全