C++设计模式之单例模式
单例模式:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用场景:
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性。
一个全局使用的类频繁地创建与销毁。
比如:windows 资源管理器,回收站等。
这应该是类设计者的职责,而不是使用者的职责。也就是说这个单例不应该由人来控制,而应该由代码来限制,强制单例。
1 #include <iostream>
2 #include <mutex>
3 #include <thread>
4 using namespace std;
5 mutex mu;//线程互斥对象
6 class Singleton {
7 public:
8 // 静态方法,供全局调用
9 static Singleton* getInstance() ;
10 void GetName() { cout << "我是单例" << endl; }
11 private:
12 //私有构造函数,不允许使用者自己生成对象
13 Singleton()
14 {
15 cout << "++++++Singleton被构造+++++" << endl;
16 }
17 Singleton(const Singleton& other);
18 static Singleton* m_instance; //静态成员变量
19 };
单例模式分为两种类型:饿汉式,懒汉式。
饿汉式:
1 // 饿汉式
2 Singleton* Singleton::getInstance()
3 {
4 return m_instance;
5 }
6 // 静态成员需要先初始化
7 Singleton* Singleton::m_instance = new Singleton();
在访问量比较大,或者可能访问的线程比较多时,可以实现更好的性能,以空间换时间。
因为是静态属性在单例类定义的时候就进行实例化,所以优点是线程是安全的,缺点是无论用户是否使用单例对象都会创建单例对象。
懒汉式:
1 //静态成员需要先初始化
2 Singleton* Singleton::m_instance = NULL;
3 // 懒汉式
4 Singleton* Singleton::getInstance()
5 {
6 // 先检查对象是否存在
7 if (m_instance == NULL)
8 m_instance = new Singleton();
9 return m_instance;
10 }
在第一次调用getInstance()的时候实例化,不调用就不会生成对象,不占据内存。适合在访问量较小时,以时间换空间。
懒汉式是线程不安全的,比如两个线程同时运行到上面的第7行后,那么两个将分别创建两个实例,不满足单例的要求。
为解决线程不安全,有了下面的加锁处理:
1 // 懒汉式 加锁(代价高)
2 Singleton* Singleton::getInstance()
3 {
4 mu.lock();
5 if (m_instance == NULL)
6 m_instance = new Singleton();
7 mu.unlock();
8 return m_instance;
9 }
但因为每次调用该方法时都要进行加锁,代价太高,因而出现了著名的双检查锁:
1 // 懒汉式 双检查锁
2 Singleton* Singleton::getInstance()
3 {
4 if (m_instance == NULL)
5 {
6 mu.lock();
7 if (m_instance == NULL)
8 m_instance = new Singleton();
9 mu.unlock();
10 }
11 return m_instance;
12 }
近乎完美。
但,由于编译器等进行优化,导致内存读写存在reorder,有时导致双检查锁的不安全性,处理办法:
1 //C++ 11版本之后的跨平台实现
2 // atomic c++11中提供的原子操作
3 #include <atomic>
4 #include <mutex>
5
6 class Singleton {
7 public:
8 // 静态方法,供全局调用
9 static Singleton* getInstance();
10 void GetName() { cout << "我是单例" << endl; }
11 private:
12 //私有构造函数,不允许使用者自己生成对象
13 Singleton() { cout << "Singleton被构造" << endl; }
14 Singleton(const Singleton& other);
15 static atomic<Singleton*> m_instance; //静态成员变量
16 static mutex m_mutex;
17 };
18 std::atomic<Singleton*> Singleton::m_instance;
19 std::mutex Singleton::m_mutex;
20 /*
21 * std::atomic_thread_fence(std::memory_order_acquire);
22 * std::atomic_thread_fence(std::memory_order_release);
23 * 这两句话可以保证他们之间的语句不会发生乱序执行。
24 */
25 Singleton* Singleton::getInstance() {
26 Singleton* tmp = m_instance.load(std::memory_order_relaxed);
27 std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
28 if (tmp == nullptr) {
29 std::lock_guard<std::mutex> lock(m_mutex);
30 tmp = m_instance.load(std::memory_order_relaxed);
31 if (tmp == nullptr) {
32 tmp = new Singleton;
33 std::atomic_thread_fence(std::memory_order_release);//释放内存fence
34 m_instance.store(tmp, std::memory_order_relaxed);
35 }
36 }
37 return tmp;
38 }
最后是main方法
1 void test01()
2 {
3 Singleton *pSingleton = Singleton::getInstance();
4 pSingleton->GetName();
5 Singleton *pSingleton1 = Singleton::getInstance();
6 pSingleton1->GetName();
7 }
8
9 void test02()
10 {
11 Singleton *pSingleton = Singleton::getInstance();
12 pSingleton->GetName();
13 }
14
15
16 // 多线程调用
17 void thread01()
18 {
19 for (int i = 0; i < 5; i++)
20 {
21 //cout << "--thread01 working...." << endl;
22 Singleton *lazy1 = Singleton::getInstance();
23 //cout << "--thread01创建单例lazy1地址:" << lazy1 << endl;
24 }
25 }
26 void thread02()
27 {
28 for (int i = 0; i < 5; i++)
29 {
30 //cout << "--thread02 working...." << endl;
31 Singleton *lazy2 = Singleton::getInstance();
32 //cout << "--thread02创建单例lazy2地址:" << lazy2 << endl;
33 }
34 }
35
36 int main()
37 {
38 // test01();
39 // test02();
40
41 thread thread1(thread01);
42 thread thread2(thread01);
43 thread1.detach();
44 thread2.detach();
45 for (int i = 0; i < 5; i++)
46 {
47 //cout << "--Main thread working..." << endl;
48 Singleton *main = Singleton::getInstance();
49 //cout << "--Main 创建单例lazy地址:" << main << endl;
50 }
51
52 system("pause");
53 return 0;
54 }
优点:
在系统内存中只存在一个对象,因此可以节约系统的资源,对于一些需要频繁创建和销毁的对象,使用单例模式无疑是提高了系统的性能;
单例模式允许可变数目的实例。基于单例模式可以进行扩展,使用与控制单例对象相似的方法获得指定个数的实例对象,既节约了系统资源,又解决了由于单例对象共享过多有损性能的问题(自行提供指定数目实例对象的类可成为多例类) 。
缺点:
由于单例模式没有抽象层,所以扩展起来很难;
单例类职责过重,在一定程度上违背了单一职责原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起;
不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
多线程模式下较复杂。