单例模式

面向对象很好的解决了“抽象”的问题,但是必不可免的要付出一定的代价。对于通常情况来讲。面向对象的成本大都可以忽略不计。但在某些情况,面向对象所带来的成本必须谨慎处理

  • 动机
    • 在软件系统中,经常有一些特殊的类,必须保证他们在系统中 只存在一个实例 ,才能保证他们的逻辑正确性、以及良好的效率
    • 这个是类设计者的责任,而不是使用者的责任

      例子

      非线程安全

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Singleton(){
      private:
      Singleton();
      Singleton(const Singleton& other);

      public:
      static Singleton* getInstance();
      static Singleton* m_instance;
      };
      Singleton* Singleton::m_instance = nullptr;

      Singleton* Singleton::getInstance(){
      if(m_instance == nullptr){
      m_instance = new Singleton();
      }
      return m_instance;
      }

      线程安全(加锁代价高)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Singleton(){
private:
Singleton();
Singleton(const Singleton& other);

public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;

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
12
13
14
15
16
17
18
19
class Singleton(){
private:
Singleton();
Singleton(const Singleton& other);

public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;

Singleton* Singleton::getInstance(){
Lock lock;// 全局加锁
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
//返回后才会解锁
}

双检测锁,但由于内存读写reorder不安全(不可以用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Singleton(){
private:
Singleton();
Singleton(const Singleton& other);

public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;

Singleton* Singleton::getInstance(){

if(m_instance == nullptr){
Lock lock;// 局部锁
if(m_instance == nullptr){
m_instance = new Singleton();
}
}
return m_instance;
//返回后才会解锁
}
  • reorder
    • 指令序列可能会
      • 可能性1
        1. 先分配内存
        2. 调用构造函数
        3. 将内存返回
    • 可能性2
      1. 先分配内存
      2. 将内存返回
      3. 调用构造函数
  • 有可能会返回一个未经初始化的对象

双检查锁,但由于内存读写reorder 不安全

cpp11版本之后的夸平台实现(volatile)

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
class Singleton(){
private:
Singleton();
Singleton(const Singleton& other);

public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::mutex;

Singleton* Singleton::getInstance(){
Singleton* tmp = Singleton.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence ,屏蔽编译器reorder
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;
}

动机

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

UML图

单例模式UML图

要点总结

  • Singleton模式中的实例构造器可以设置为protect以允许子类派生
  • Singleton模式一般不要支持拷贝构造和Clone接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背
  • 如何实现多线程下安全的Singleton?注意对双检查锁的正确实现