【多线程】线程安全的单例模式

文章目录

什么是单例模式

单例模式是一个设计模式,其目的是确保一个类只有一个实例,并提供一个全局的访问点来访问该实例。单例模式常用于需要控制资源数量的场景,比如数据库连接池、日志管理器等。下面介绍常见的单例模式的实现方式。

饿汉实现方式

观察下面代码:

cpp 复制代码
class EagerSingleton {
private:
    // 私有构造函数,防止外部实例化
    EagerSingleton() {}

    // 禁用拷贝构造函数和赋值运算符
    EagerSingleton(const EagerSingleton&) = delete;
    EagerSingleton& operator=(const EagerSingleton&) = delete;

    // 静态实例,类加载时初始化
    static EagerSingleton instance;

public:
    // 提供全局访问点
    static EagerSingleton& getInstance() {
        return instance;
    }
};

// 初始化静态实例
EagerSingleton EagerSingleton::instance;

要想实现单例,从两个方面入手:1.如何实现禁止构造2个或者2个以上的实例化对象 。2.如何实现只构造一个对象

对于前者,我们可以将构造函数私有化,这样就不能在类外部构造对象了 。那怎么保证拥有一个实例化对象呢?这一点可以在类中使用static修饰一个该类的实例对象 ,这样就能在程序加载的时候自动创建一个实例化对象,由于static成员属于类本身的特性,所以能使用私有的构造函数,且只会加载一次。

要想使用这个单例,就需要一个static修饰的成员方法专门用来提供全局的访问点,因为static成员函数可以直接通过类名使用,不需要创建实例化对象。 此外静态成员变量需要在类外部初始化。

饿汉实现模式的特点

  • 类加载的时候实例化
  • 私有化构造函数
  • 线程安全:由于实例化是在类加载的时候完成的,且类加载的过程中由C++标准保证了线程安全,因此不需要额外的同步机制来保证线程安全。

懒汉实现方式

观察下面代码

cpp 复制代码
class LazySingleton {
private:
    // 私有的静态指针变量,用于指向唯一的实例
    static LazySingleton* instance;

    // 私有构造函数,防止外部实例化
    LazySingleton() {}

public:
    // 提供全局访问点
    static LazySingleton* getInstance() {
        if (instance == nullptr) {
            instance = new LazySingleton();
        }
        return instance;
    }
};

// 初始化静态指针变量
LazySingleton* LazySingleton::instance = nullptr;

注意其中和饿汉实现方式的区别,懒汉实现单例模式中static修饰的是一个实例对象的指针。这样一来,类加载的时候就不会构造自动一个实例化对象。具体的观察这一段代码:

cpp 复制代码
static LazySingleton* getInstance() {
        if (instance == nullptr) {
            instance = new LazySingleton();
        }
        return instance;
    }

这一段代码保证了该类只能实例化对象一次,且只在第一次调用getInstance() 函数时才会创建。这样一来也能实现单例模式。但是和饿汉模式不一样的是,这种单例模式只在第一次使用的时候实例化一个对象,而饿汉模式是程序开始时不管你用不用都会实例化一个。这种机制又被称为延迟实例化机制 。此外,这种机制在多线程模式下是不安全的。比如可能会有多个线程
new LazySingleton();

为了保证线程安全,我们需要使用锁来保证创建实例化对象时同步。例如下面代码:

cpp 复制代码
#include <mutex>

class LazySingleton {
private:
    static LazySingleton* instance;
    static std::mutex mtx;
    LazySingleton() {}

public:
    static LazySingleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (instance == nullptr) {
            instance = new LazySingleton();
        }
        return instance;
    }
};

// 初始化静态变量
LazySingleton* LazySingleton::instance = nullptr;
std::mutex LazySingleton::mtx;

上面加锁方式在每次调用getInstance()这个函数的时候都会加一次锁,频繁的调用该函数可能会造成一定的性能问题。

对于上面加锁方式还可以进一步进行优化,例如采用双重检查锁定,具体优化的代码如下:

cpp 复制代码
#include <mutex>

class LazySingleton {
private:
    static LazySingleton* instance;
    static std::mutex mtx;
    LazySingleton() {}

public:
    static LazySingleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new LazySingleton();
            }
        }
        return instance;
    }
};

// 初始化静态变量
LazySingleton* LazySingleton::instance = nullptr;
std::mutex LazySingleton::mtx;

这样一来,只有在首次创建对象时会上锁,其余时候都不会也不需要。

懒汉实现方式的特点

  • 线程不安全
  • 延迟实例化
  • 需要使用互斥锁保证线程安全
    具体原因分析上面已经阐述过了,这里就不做过多分析了。
相关推荐
天天喝旺仔9 小时前
Linux 性能排查实战:用 perf、strace 与火焰图定位 CPU、内存与 IO 瓶颈
linux·性能优化·运维开发
LuminousCPP9 小时前
Linux系统编程(二):从目录树到命令执行|路径、环境变量、alias 与文件操作入门
linux·经验分享·笔记
第十人i9 小时前
Linux 系统初始化配置清单:新服务器快速部署
linux·服务器
LongRunning9 小时前
【Linux】RK3568-OpenCV+YOLO(七)
linux
j7~9 小时前
【Linux】三十八.C++ 手写 HTTP 服务器:裸 socket + fork 多进程,从 HTTP 报文解析到浏览器打开网页
linux·网络·网络协议·学习·http·网络编程
傲世仙尊9 小时前
设计者的取舍-路径fork的诞生与全局文件队列
linux
逐流人10 小时前
Containerd容器管理实战:从架构原理到nerdctlcrictl工具链
linux·运维·云原生·容器·云计算·containerd
Tairitsu_H10 小时前
[Linux] 编辑器vim、编译器和动静态链接
linux·vim·gcc·g++·动态链接·静态链接
lzx_00210 小时前
Linux指令(一) 简单了解部分指令
linux
小则又沐风a10 小时前
深入理解TCP协议----滑动窗口,流量控制
linux·网络协议·tcp