设计模式-单例模式

单例模式

0、静态函数变量版本

利用C++特性

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
43
44
45
46
47
48
49
template <class T>
class SingleTon {
public:
static T& GetInstance() {
static T ins;
return ins;
}
SingleTon(const SingleTon&) = delete;
SingleTon& operator=(const SingleTon&) = delete;
virtual ~SingleTon() {}
protected:
SingleTon() {}
};

class Apple : public SingleTon<Apple> {
friend SingleTon<Apple>; // 友元
public:
void show() {
cout << __FUNCTION__ << endl;
}
~Apple() {
cout << __FUNCTION__ << endl;
}
protected:
Apple() {}
};

class Orange : public SingleTon<Orange> {
friend SingleTon<Orange>; // 友元
public:
~Orange() {
cout << __FUNCTION__ << endl;
}
void show() {
cout << __FUNCTION__ << endl;
}
protected:
Orange() {}
};

int main() {
Apple::GetInstance().show();
cout << &Apple::GetInstance() << endl;
Apple::GetInstance().show();
cout << &Apple::GetInstance() << endl;
Orange::GetInstance().show();
Orange::GetInstance().show();
return 0;
}

1、普通版本(高并发效率不足)(安全)

  • 在类中添加一个私有静态成员单例实例的指针。
  • 声明一个公有静态构建方法用于获取单例实例的指针。
  • 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  • 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  • 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。
    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
    class Singleton {
    protected:
    Singleton(const string& val) : data(val) {} // 构造函数
    ~Singleton() {}
    static Singleton* _instance; // 静态:实例的指针
    static mutex _mutex;
    string data; // 代表类内的资源

    public:
    Singleton(const Singleton& another) = delete; // 禁止拷贝构造
    Singleton& operator=(const Singleton& another) = delete; // 禁止赋值
    static Singleton* getInstance(const string& val); // 静态:获取实例
    void show() { cout << "data: " << data << endl; } // 测试
    };

    // 静态成员需要在类外定义
    Singleton* Singleton::_instance = nullptr;
    mutex Singleton::_mutex;

    Singleton* Singleton::getInstance(const string& val) {
    lock_guard<mutex> lock(_mutex); // 出作用域自动释放
    if (_instance == nullptr) {
    _instance = new Singleton(val);
    }
    return _instance;
    }

    int main() {
    Singleton* a = Singleton::getInstance("aaa");
    a->show(); // "aaa" ok
    Singleton* b = Singleton::getInstance("bbb");
    b->show(); // "aaa" ok
    return 0;
    }

2、双检查锁(有隐患)

reorder问题:通常new通常有三步:1、分配一块内存空间;2、执行构造器;3、返回指针; 但是由于编译器有时会进行优化,执行顺序变为132:即分配空间后先返回指针,再执行构造器 这样就会到来危险,比如线程A执行new,132,3执行完;线程B发现m_instance非空返回,然后外部直接用这个指针会出错

1
2
3
4
5
6
7
8
9
10
//双检查锁,但由于内存读写reorder不安全(不安全)
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){ // 第一次检查,主要解决性能问题,毕竟读操作不需要上锁
Lock lock;
if (m_instance == nullptr) { // 第二次检查,解决核心问题:只能new单个
m_instance = new Singleton();
}
}
return m_instance;
}

3、双检查锁(安全)

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;
}

参考资料