【单例模式】

发布于:2025-08-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

概述

一个类不管创建多少次对象,永远只能得到该类型的一个对象的实例。

常用到的比如日志模块 ,数据库模块

    饿汉:在类加载时就创建单例对象,因此它是线程安全的,因为对象的创建在程序启动时就已经完成,不存在多线程同时创建对象的问题。
    懒汉:在第一次使用单例对象时才创建对象,这种方式在多线程环境下需要考虑线程安全问题,通常使用互斥锁来保证对象只被创建一次。

1. 一个私有构造函数(确保只能单例类自己创建实例):
    单例类通常会将其构造函数设为私有,以防止外部代码直接实例化对象。
    
2. 一个私有静态变量(确保只有一个实例)
    单例类通常包含一个私有的静态变量,用于保存该类的唯一实例。
    
3. 一个公有静态函数(给使用者提供调用方法)

单例模式的6种实现

1、懒汉式(线程不安全)

class Singleton {
private:
    // 延迟初始化:初始为 nullptr,第一次使用时创建
    static Singleton* instance;

    // 私有构造:禁止外部 new
    Singleton() {}

    // 禁用拷贝(防止复制实例)
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    // 全局访问点
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton(); // 第一次调用时创建实例
        }
        return instance;
    }
};

// 静态成员类外初始化
Singleton* Singleton::instance = nullptr;

先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。

优点:延迟了实例化,如果不需要使用该类,就不会被实例化,只有在需要时才创建实例,避免了资源浪费。

缺点:线程不安全,多线程环境下,如果多个线程同时进入了` if (instance == null) `,若此时还未实例化,也就是`instance == null`,那么就会有多个线程执行 `instance = new Singleton(); `,就会实例化多个实例;
 

2、饿汉式(线程安全)

class Singleton {
private:
    static Singleton instance;

    Singleton() {}

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    static Singleton& getInstance() {
        return instance;
    }
};

Singleton Singleton::instance;      // 静态成员类外初始化(程序启动时执行)

先不管需不需要使用这个实例,直接先实例化好实例(饿死鬼一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了

优点:提前实例化好了一个实例,避免了线程不安全问题的出现,

缺点:直接实例化了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会使操作系统的资源浪费。
 

3、懒汉式(线程安全)

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx; // 互斥锁:保证线程安全

    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    // 整个方法加锁:每次调用都需获取锁
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx); // 自动加锁/解锁,避免死锁
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

实现和线程不安全的懒汉式 几乎一样,唯一不同的点是,在get方法上 加了一把锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。

优点:延迟实例化,节约了资源,并且是线程安全的。

缺点:虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。

4、双重检查锁实现(线程安全)

class Singleton {
private:
    // volatile确保多线程下实例状态可见
    static volatile Singleton* instance;
    static std::mutex mtx;

    Singleton() {}

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    // 双重检查锁定实现线程安全的懒加载
    static Singleton* getInstance() {
        // 第一次检查:避免频繁加锁
        if (instance == nullptr) {
            // 加锁:确保只有一个线程进入初始化
            std::lock_guard<std::mutex> lock(mtx);
            // 第二次检查:防止多线程同时通过第一次检查
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return const_cast<Singleton*>(uniqueInstance);
    }
};

// 静态成员初始化
volatile Singleton* Singleton::uniqueInstance = nullptr;
std::mutex Singleton::mtx;

双重检查锁相当于是改进了线程安全的懒汉式。线程安全的懒汉式的缺点是性能降低了,造成的原因是因为即使实例已经实例化,依然每次都会有锁。

而现在,我们将锁的位置变了,并且多加了一个检查。也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。
 

volatile 的作用

`new Singleton()` 底层分 3 步:分配对象 -> 初始化对象 -> 指针指向内存

编译器/CPU可能重排上述顺序,1->3->2,导致B拿到未初始化的实例,`volatile` 禁止重排,确保“初始化完成后才能赋值指针”,同时保证对线程对`instance`的读写可见
 

5、静态内部类的实现

class Singleton {
private:
    // 私有构造
    Singleton() {}

    // 禁用拷贝
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态内部类:仅在被访问时加载
    static class SingletonHolder {
    public:
        // 内部类静态成员:程序启动时初始化(线程安全)
        static Singleton instance;
    };

public:
    // 全局访问点:调用时触发内部类加载
    static Singleton& getInstance() {
        return SingletonHolder::instance;
    }
};

// 静态内部类的静态成员初始化
Singleton Singleton::SingletonHolder::instance;

延迟加载:外部类 `Singleton` 加载时,内部类 `SingletonHolder` 不加载;仅调用 `getInstance()` 时,内部类才加载并初始化 `instance`。
线程安全:C++ 静态成员初始化在单线程阶段执行,天然避免并发问题

缺点:无法主动销毁实例(实例随程序退出释放)

6、枚举类(线程安全 )

// 枚举类:默认线程安全,且天然防止拷贝
enum class Singleton {
    INSTANCE; // 唯一枚举实例

    // 枚举类成员方法(扩展功能)
    void doSomething() {
        std::cout << "Singleton 执行任务" << std::endl;
    }
};

// 全局访问宏(可选,简化调用)
#define SINGLETON Singleton::INSTANCE

枚举类的三个特点:
1. 成员唯一:枚举里的每个成员(INSTANCE)是全局唯一的,整个程序只有一个,不能像普通类那样new多个对现象
2. 禁止拷贝
3. 自动初始化:枚举的成员会在程序启动时自动初始化(饿汉),无需手动创建

使用场景

(1)频繁实例化然后又销毁的对象,使用单例模式可以提高性能

(2)经常使用的对象,但实例化时耗费时间或者资源多,如数据库连接池,使用单例模式,可以提高性能,降低资源损坏

(3)使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信

具体如:日志,数据库连接池,计数器等


网站公告

今日签到

点亮在社区的每一天
去签到