cen's blog cen's blog
首页
  • 编程文章

    • Markdown使用
    • C语言程序的运行
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • Markdown使用
    • C语言程序的运行
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • 类和对象
  • 内存管理
  • 泛型模板
  • string
  • vector
  • list
  • stack和queue
  • priority_queue
  • 继承
  • 多态
  • set和map
  • bitset
  • C++11
  • 异常
  • 智能指针
  • 特殊类设计
  • function和bind
  • 右值引用和移动语义
  • thread
  • 单例模式
    • C++学习笔记
    cen
    2026-04-14
    目录

    单例模式

    单例模式指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有以下两种实现方式:

    # 饿汉模式

    饿汉模式就是在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象。
    饿汉式单例是在类加载时就创建实例。它的优点是不需要考虑多线程同步问题,因为在类加载时就完成了初始化,在多线程高并发环境下频繁使用,性能要求较高;缺点是无论是否使用都会占用内存。也就是没有线程安全问题。

    做法:

    • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
    • 提供一个指向单例对象的 static 指针,并在程序入口之前完成单例对象的初始化。
    • 提供一个全局访问点获取单例对象。
    // 饿汉模式
    // 优点:简单
    // 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
    class HungrySingleton {
    public:
        // 返回引用,避免指针操作
        static HungrySingleton& GetInstance() {
            return instance;
        }
    
        void DoSomething() {
            std::cout << "Doing something..." << std::endl;
        }
    
    private:
        // 私有构造函数
        HungrySingleton() {} = default;
    
        ~HungrySingleton() {}
    
        // 禁止拷贝和赋值
        HungrySingleton(const HungrySingleton&) = delete;
        HungrySingleton& operator=(const HungrySingleton&) = delete;
    
        // 静态成员变量,在 main 之前初始化
        static HungrySingleton instance;
    };
    
    // 在全局作用域初始化(main 函数之前执行)
    HungrySingleton HungrySingleton::instance;
    
    int main() {
        HungrySingleton::GetInstance().DoSomething();
        return 0;
    }
    
    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

    # 懒汉模式

    如果单例对象构造十分耗时或者占用很多资源,比如加载插件,初始化网络连接啊,读取文件等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。所以这种情况使用懒汉模式(延迟加载)更好。

    • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
    • 提供一个指向单例对象的 static 指针,并在程序入口之前先将其初始化为空。
    • 提供一个全局访问点获取单例对象。
    // 懒汉模式
    // 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
    // 缺点:复杂
    class LazySingleton {
    public:
        static LazySingleton* GetInstance() {
            // 第一次检查(无锁)
            if (instance == nullptr) {
                // 加锁
                std::lock_guard<std::mutex> lock(mutex);
                // 第二次检查(有锁)
                if (instance == nullptr) {
                    instance = new LazySingleton();
                }
            }
            return instance;
        }
    
        void DoSomething() {
            std::cout << "Doing something..." << std::endl;
        }
    
        // 手动释放
        static void Destroy() {
            std::lock_guard<std::mutex> lock(mutex);
            delete instance;
            instance = nullptr;
        }
    
    private:
        LazySingleton() {} = default;
    
        ~LazySingleton() {}
    
        LazySingleton(const LazySingleton&) = delete;
        LazySingleton& operator=(const LazySingleton&) = delete;
    
        static LazySingleton* instance;
        static std::mutex mutex;
    };
    
    // 静态成员初始化
    LazySingleton* LazySingleton::instance = nullptr;
    std::mutex LazySingleton::mutex;
    
    int main() {
        // 第一次调用,会创建对象
        LazySingleton::GetInstance()->DoSomething();
        // 第二次调用,直接返回已有对象
        LazySingleton::GetInstance()->DoSomething();
        return 0;
    }
    
    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
    50
    51
    52

    双重检查锁定(Double-Checked Locking)

    双重检查锁定是一种用于懒汉式单例模式的优化技术,主要目的是在多线程环境下创建单例对象时,既保证线程安全性,又避免不必要的加锁操作,提高性能。

    1. 为什么需要双重检查锁定? 当我们在多线程环境中创建单例时,如果不进行适当的同步控制,可能会导致多个线程同时创建实例,从而产生多个对象,违背了单例模式的初衷。为了解决这个问题,我们通常会对获取实例的过程加锁,但是简单的加锁会带来性能问题,因为加锁和解锁是一个耗时操作,会影响性能。

    2. 双重检查锁定是如何优化的?

    双重检查锁定通过在加锁前后分别检查实例是否为空,减少了不必要的加锁操作。具体步骤如下:

    static Singleton* getInstance() {
        // 第一次检查(不加锁)
        if (instance == nullptr) {
            // 加锁,防止多个线程同时创建实例
            std::lock_guard<std::mutex> lock(mutex_);
    
            // 第二次检查(加锁后)
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    • 第一次检查:在加锁前检查 instance 是否为空。如果已经有实例了,就直接返回,无需加锁。
    • 加锁:如果 instance 为空,说明实例还没有被创建,进行加锁操作,确保只有一个线程能够进入创建实例的代码块。
    • 第二次检查:加锁后,再次检查 instance 是否为空。这样做是因为在第一个线程加锁前,其他线程可能已经创建了实例。只有在 instance 仍然为空时,才会创建新实例。

    自 C++11 起,推荐利用静态局部变量的特性实现线程安全的单例模式:

    static Singleton& getInstance() {
        static Singleton instance; // C++11保证局部静态变量的线程安全
        return instance;
    }
    
    1
    2
    3
    4

    在这个实现中,getInstance() 方法中的静态局部变量 instance 只会在第一次调用时初始化,并且 C++11 保证其线程安全。

    #设计模式
    上次更新: 2026/04/14, 21:59:44
    thread

    ← thread

    最近更新
    01
    分治和归并
    04-14
    02
    事务特性
    04-10
    03
    递归、搜索和回溯
    04-08
    更多文章>
    Theme by Vdoing | Copyright © 2024-2026 京ICP备2020044002号-3 京公网安备11010502056119号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式