0%

设计模式之单例模式和享元模式

设计模式基础(8):单例模式&享元模式

包含单例模式&享元模式两种模式中的C++示例代码、面向的问题、图解两种模式核心思想

单例模式

面向的需求

  • 保证一个类仅有一个实例,并提供一个该实例的全局访问点。

示例代码

1
2
3
4
5
6
7
8
9
10
11
//基础代码部分
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;
1
2
3
4
5
6
7
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
1
2
3
4
5
6
7
8
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
1
2
3
4
5
6
7
8
9
10
11
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {

if(m_instance==nullptr){
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
  • reorder不安全
  • 这个是指通常意义上,采用new来获得一个对象,按顺序为以下三步(申请内存→调用构造函数得到对象→返回该对象地址(指针))。此时双检查锁是完全没有问题的,但是,编译器可能会为了效率进行优化,采用新的顺序(申请内存→返回该对象地址(指针)→调用构造函数得到对象)。由于CPU是在汇编指令层面进行时间轮片,就有可能,指针地址得到了,但是没有实际对象,从而引发问题。这个称之为reorder不安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}

代码思想分析

可以看到在版本的逐个迭代中,最终是得到了多线程下相对完美的解决方法。同时需要指出的是,在c#和Java语言中,直接采用volatile关键字便可以避免reorder不安全。

关键点

  • static关键字的使用。
  • private下设计构造函数,拷贝构造函数。
  • static修饰下,必须类外实现。
  • 线程安全问题&reorder安全问题

享元模式

面向的需求

  • 系统中存在大量的细粒度的对象,简单的面向对象设计思路会带来很高内存方面的运行代价。

示例代码

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
class Font {
private:

//unique object key
string key;

//object state
//....

public:
Font(const string& key){
//...
}
};
ß

class FontFactory{
private:
map<string,Font* > fontPool;

public:
Font* GetFont(const string& key){

map<string,Font*>::iterator item=fontPool.find(key);

if(item!=footPool.end()){
return fontPool[key];
}
else{
Font* font = new Font(key);
fontPool[key]= font;
return font;
}

}

void clear(){
//...
}
};

代码思想分析

享元模式
先请求获得一个享元模式中的对象,收到请求后的享元工厂判断自己的享元池是否已有,若没有则创建,若有则直接返回已有对象。

关键点

  • 享元模式最重要的是确定何时采用享元,要合理的评估怎样的数量级下需要使用享元。
  • 享元的实现的重点思想:有则返回,无则创建并记录进池。