设计模式——单例模式

单例模式(Singleton)

单例模式是一种创建型设计模式, 保证一个类只有一个实例,并提供一个访问它的全局访问点

单例模式解决了两个问题:

  1. 保证一个类只有一个实例。控制类的实例数量的常见原因是控制某些共享资源(例如数据库或文件)的访问权限。
  2. 为该实例提供一个全局访问节点
    但是单例模式违反了单一职责原则

单例模式的实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例
  2. 声明一个共有静态构造方法用于获取单例实例
  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用
  5. 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。
c++ 复制代码
#include <iostream>
#include <string>

using namespace std;

class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            instance = new Singleton();
            cout << "首次创建实例" << endl;
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton(){
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
};

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

// 客户端代码
static void Client()
{
    Singleton &s1 = Singleton::GetInstance();
    Singleton &s2 = Singleton::GetInstance();
    Singleton &s3 = Singleton::GetInstance();
}

int main()
{
    Client();

    return 0;
}

输出为

复制代码
首次创建实例
重复创建,返回同一个实例
重复创建,返回同一个实例

多线程时候的单例模式

如果main函数改写成

c++ 复制代码
int main()
{
    // 线程容器
    vector<thread> threads;

    // 创建100个线程
    for (int i = 0; i < 10; i++){
        threads.emplace_back(Client);
    }
    // 等待所有线程完成
    for(auto& th:threads){
        th.join();
    }

    return 0;
}

注意将s2和s3注释掉了

输出变为

复制代码
重复创建,返回同一个实例首次创建实例重复创建,返回同一个实例
首次创建实例
首次创建实例

重复创建,返回同一个实例
重复创建,返回同一个实例
重复创建,返回同一个实例

重复创建,返回同一个实例重复创建,返回同一个实例

出现了线程安全问题,有多个线程首次创建了实例

原因是GetInstance方法中条件判断(if (instance == nullptr))和实例创建(instance = new Singleton();)之间的代码片段并不是原子操作,可能会有多个线程同时进入这个条件判断,并且创建多个实例。

加锁的版本

c++ 复制代码
class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 加锁
        lock_guard<mutex> lock(mutex_);
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            instance = new Singleton();
            cout << "首次创建实例" << endl;
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton()
    {
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
    // 锁
    static mutex mutex_;
};

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

加锁解决了线程安全的问题,但是新的问题来了,不管三七二十一都加锁,这样很影响性能

双重检查锁

c++ 复制代码
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

using namespace std;

class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            // 加锁
            lock_guard<mutex> lock(mutex_);
            // 第二次检查
            if (instance == nullptr)
            {
                instance = new Singleton();
                cout << "首次创建实例" << endl;
            }
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton()
    {
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
    // 锁
    static mutex mutex_;
};

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


// 客户端代码
static void Client()
{
    Singleton &s1 = Singleton::GetInstance();
    // Singleton &s2 = Singleton::GetInstance();
    // Singleton &s3 = Singleton::GetInstance();
}


int main()
{
    // 线程容器
    vector<thread> threads;

    // 创建100个线程
    for (int i = 0; i < 10; i++){
        threads.emplace_back(Client);
    }
    // 等待所有线程完成
    for(auto& th:threads){
        th.join();
    }

    return 0;
}

饿汉式

上面的代码是单例模式的懒汉式。所谓懒汉式,就是在程序需要用到这个类的实例的时候采取创建,所以在多线程的场景下存在线程安全问题。

饿汉式,是指在程序在首次加载类的时候就会实例化,所以不存在线程安全问题。

代码如下

c++ 复制代码
// 饿汉式实现
#include <iostream>  
#include <string>  
#include <thread>  
#include <vector>  
  
using namespace std;  
  
class Singleton  
{  
public:  
    // 本类实例的唯一全局访问点  
    static Singleton &GetInstance()  
    {  
        // 直接返回已经初始化的实例  
        return instance;  
    }  
  
    // 禁止拷贝构造函数和赋值操作符  
    Singleton(const Singleton&) = delete;  
    Singleton& operator=(const Singleton&) = delete;  
  
private:  
    // 私有的静态实例,在类声明时初始化  
    static Singleton instance;  
  
    // 私有的构造函数,防止外部代码创建类的实例  
    Singleton()  
    {  
        cout << "首次创建实例" << endl;  
    }  
};  
  
// 静态成员需要在类外定义  
Singleton Singleton::instance;  
  
// 客户端代码  
static void Client()  
{  
    Singleton &s = Singleton::GetInstance();  
}  
  
int main()  
{  
    // 线程容器  
    vector<thread> threads;  
  
    // 创建10个线程  
    for (int i = 0; i < 10; i++)  
    {  
        threads.emplace_back(Client);  
    }  
  
    // 等待所有线程完成  
    for(auto& th:threads)  
    {  
        th.join();  
    }  
  
    return 0;  
}
相关推荐
李广坤17 小时前
状态模式(State Pattern)
设计模式
李广坤19 小时前
观察者模式(Observer Pattern)
设计模式
李广坤20 小时前
中介者模式(Mediator Pattern)
设计模式
李广坤20 小时前
迭代器模式(Iterator Pattern)
设计模式
李广坤20 小时前
解释器模式(Interpreter Pattern)
设计模式
阿无,1 天前
java23种设计模式之前言
设计模式
Asort1 天前
JavaScript设计模式(八):组合模式(Composite)——构建灵活可扩展的树形对象结构
前端·javascript·设计模式
数据智能老司机1 天前
数据工程设计模式——数据基础
大数据·设计模式·架构
笨手笨脚の1 天前
设计模式-代理模式
设计模式·代理模式·aop·动态代理·结构型设计模式
Overboom1 天前
[C++] --- 常用设计模式
开发语言·c++·设计模式