单例模式概念
定义
单例模式 Singleton:该类只能实例化一个对象的设计模式。
分类
- 懒汉模式:延时加载,在这个唯一的对象使用时才加载。
优点
- 资源利用率高,使用时才创建,不存在浪费。
- 加载类速度快,不需要创捷对象。
缺点
- 存在线程安全问题,需要引入同步机制(锁、信号量)。创建唯一对象不是原子操作,判空也不是原子操作。
- 运行获取速度慢,多线程同步带来额外开销。
- 饿汉模式:贪婪加载,在类加载时就生成唯一的对象。
优点
- 线程安全的单例模式。因为在加载的时候就创建了,所以不会有竞争。
- 获取对象速度快,因为已经创建好了,只需要获取就可以。
缺点
- 存在资源浪费的可能。因为可能只是加载这个类却不使用他。
- 类加载速度慢,因为需要创建对象。
设计思想
如何控制,达到只生成一个对象的目的。
- 对生成对象进行控制,将构造函数设计为私有。这样类外就不能调用构造函数,达到控制对象生成的目的。
- 对拷贝构造函数进行控制,将其设为私有并不实现,或者进行删除(C++11)。
提供对外接口,生成唯一对象。
这个接口需要判断是否只生成了一个对象。
- 这个函数的返回值需要返回一个对象,分三种情况。
- 返回对象类名,存在临时对象,会发生拷贝,不符合单例模式要求。
- 返回对象引用,没有拷贝发生。
- 返回对象的指针,没有拷贝发生。
- 修饰关键字设置,从调用角度考虑。
- 依赖对象来调用,obj.func() 来生成。但是此时对象未生成,所以不符合要求。
- 设计为静态函数,不依赖对象调用。通过类作用域来调用 ClassName::func() 来生成。
- 有一个标识,来标识唯一的对象。
- 为了防止这个接口被调用很多次,定义一个唯一标识对象的指针,这个指针不依赖于对象。所以还是定义为静态变量,类外初始化为 nullptr。
- 指针为空,那就说明对象还没有生成。如果不为空,就说明已经生成了。
懒汉模式代码
单线程版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream> using namespace std;
class LazySingleton { public: static LazySingleton *GetInstance() { if (instance_ == nullptr) { instance_ = new LazySingleton(); } return instance_; } private: LazySingleton() { cout << "create singleton object:" << this << endl; } LazySingleton(const LazySingleton& s) = delete; static LazySingleton *instance_; }; LazySingleton* LazySingleton::instance_ = nullptr; int main() { LazySingleton *obj1 = LazySingleton::GetInstance(); LazySingleton *obj2 = LazySingleton::GetInstance(); LazySingleton *obj3 = LazySingleton::GetInstance(); cout << "obj1:" << obj1 << endl; cout << "obj2:" << obj2 << endl; cout << "obj3:" << obj3 << endl; return 0; }
|
输出:
1 2 3 4 5
| @└────> # ./a.out create singleton object:0x1861eb0 obj1:0x1861eb0 obj2:0x1861eb0 obj3:0x1861eb0
|
这种懒汉模式的单例,在单线程下是没问题的,但是多线程下有竞争就会出现问题。比如两个线程都进行完了 instance_ 的判断,并且都为空,那么他们都会调用那个构造函数。
多线程版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> #include <mutex> #include <thread> using namespace std;
class LazySingleton { public: static LazySingleton *GetInstance() { m_.lock(); if (instance_ == nullptr) { instance_ = new LazySingleton(); } m_.unlock(); return instance_; } private: LazySingleton() { cout << "create singleton object:" << this << endl; } LazySingleton(const LazySingleton& s) = delete; static LazySingleton *instance_; static mutex m_; }; LazySingleton* LazySingleton::instance_ = nullptr; mutex LazySingleton::m_; int main() { thread t1([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t2([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t3([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); t1.join(); t2.join(); t3.join(); return 0; }
|
结果:
1 2 3 4 5
| @└────> # ./a.out thread id:139862718146304 instance pointer:create singleton object:0x7f344c000f70 0x7f344c000f70 thread id:139862709753600 instance pointer:0x7f344c000f70 thread id:139862701360896 instance pointer:0x7f344c000f70
|
加锁,保证一个线程在对临界资源进行操作时不能被其他线程打断。用互斥锁即可,但是如果每次调用都需要进行加锁解锁,在第一个对象已经生成之后,在读的层面不需要进行加锁了。加锁解锁会导致效率低下。
可以把上面的加锁部分套在一个 if 判断里,这样下个线程进来的时候就不用试图加锁了。
改进版代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <iostream> #include <mutex> #include <thread> using namespace std;
class LazySingleton { public: static LazySingleton *GetInstance() { if (instance_ == nullptr) { m_.lock(); if (instance_ == nullptr) { instance_ = new LazySingleton(); } m_.unlock(); } return instance_; } private: LazySingleton() { cout << "create singleton object:" << this << endl; } LazySingleton(const LazySingleton& s) = delete; static LazySingleton *instance_; static mutex m_; }; LazySingleton* LazySingleton::instance_ = nullptr; mutex LazySingleton::m_; int main() { thread t1([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t2([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t3([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); t1.join(); t2.join(); t3.join(); return 0; }
|
结果和上面是一样的,这里就不展示了。
C++11 的多线程版本
在 C++11 中,引入了 std::call_once,可以更便捷的实现上述功能。如需使用,只需要 #include 即可,简单来说 std:call_once 的作用,确保函数或代码片段在多线程环境下,只需要执行一次,常用的场景如 Init() 操作或一些系统参数的获取等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream> #include <mutex> #include <thread> using namespace std;
class LazySingleton { public: static LazySingleton *GetInstance() { std::call_once(instnace_flag_, []() { instance_ = new LazySingleton(); }); return instance_; } private: LazySingleton() { cout << "create singleton object:" << this << endl; } LazySingleton(const LazySingleton& s) = delete; static LazySingleton *instance_; static once_flag instnace_flag_; }; LazySingleton* LazySingleton::instance_ = nullptr; once_flag LazySingleton::instnace_flag_; int main() { thread t1([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t2([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); thread t3([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << LazySingleton::GetInstance() << endl; }); t1.join(); t2.join(); t3.join(); return 0; }
|
结果:
1 2 3 4 5
| @└────> # ./a.out thread id:140655525984000 instance pointer:create singleton object:0x7fece4000f70 0x7fece4000f70 thread id:140655517591296 instance pointer:0x7fece4000f70 thread id:140655509198592 instance pointer:0x7fece4000f70
|
饿汉模式代码
如果我们在线程开启前,已经将唯一的对象生成,就不会出现线程不安全的问题。即唯一的对象在main函数调用前已经生成,就不会产生线程安全问题。所以:
- 在未进入 main 函数时,就完成唯一对象的生成,那么就是在程序加载时已经完成,存放在 .data段 的数据有这样的特点,所以我们可以直接给静态成员变量生成对象。
- 接口只用返回生成好的对象指针即可。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> #include <thread> using namespace std;
class HungerSingleton { public: static HungerSingleton *GetInstance() { return instance_; } private: HungerSingleton() { cout << "create singleton object:" << this << endl; } HungerSingleton(const HungerSingleton& s) = delete; static HungerSingleton *instance_; }; HungerSingleton* HungerSingleton::instance_ = new HungerSingleton();
int main() { thread t1([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << HungerSingleton::GetInstance() << endl; }); thread t2([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << HungerSingleton::GetInstance() << endl; }); thread t3([]() { cout << "thread id:" << this_thread::get_id() << " instance pointer:" << HungerSingleton::GetInstance() << endl; }); t1.join(); t2.join(); t3.join(); return 0; }
|
1 2 3 4 5
| @└────> # ./a.out create singleton object:0x2343eb0 thread id:140504409663232 instance pointer:0x2343eb0 thread id:140504401270528 instance pointer:0x2343eb0 thread id:140504392877824 instance pointer:0x2343eb0
|