📖 什么是单例模式
1. 生活中的例子
想象一个公司的 CEO:
整个公司 → [唯一的 CEO] → 所有员工 (单例) 都向同一个 CEO 汇报
关键点:
单例模式确保类只有一个实例并提供全局访问点。文章介绍其定义、适用场景(如打印队列、数据库连接池)及四种实现方式(饿汉式、懒汉式、双重检查锁、C++11 静态局部变量)。重点推荐 C++11 静态局部变量方案,因其天然线程安全且代码简洁。同时涵盖内存泄漏、多线程竞争及继承问题的解决方案。

想象一个公司的 CEO:
整个公司 → [唯一的 CEO] → 所有员工 (单例) 都向同一个 CEO 汇报
关键点:
单例模式(Singleton Pattern) 是一种创建型设计模式:
全局访问点 → [唯一实例] → 所有调用者 getInstance() 单例对象 共享同一个对象
核心要素:
调用者通过 getInstance() 获取 Singleton 实例。
根据 GoF(Gang of Four)《设计模式》原书的定义:
Intent: 'Ensure a class has only one instance, and provide a global point of access to it.'
(确保一个类只有一个实例,并提供一个全局访问点)
核心动机:
'The most common reason for this is to control access to some shared resource — for example, a database or a file.'
(最常见的原因是控制对共享资源的访问——例如数据库或文件)
办公室场景:一台打印机,多个员工。如果没有统一管理,打印内容混乱、卡纸、冲突。
// 多个打印管理器实例,导致打印任务混乱
class PrinterSpooler {
public:
void addJob(const std::string& document) {
jobQueue_.push(document);
}
void processJobs() {
while (!jobQueue_.empty()) {
std::cout << "打印:" << jobQueue_.front() << std::endl;
jobQueue_.pop();
}
}
private:
std::queue<std::string> jobQueue_;
};
int main() {
PrinterSpooler spooler1; // 队列 1
PrinterSpooler spooler2; // 队列 2 - 又一个队列!
PrinterSpooler spooler3; // 队列 3 - 再来一个!
spooler1.addJob("文档 A");
spooler2.addJob("文档 B"); // 进入了不同的队列!
spooler3.addJob("文档 C"); // 打印顺序无法统一管理
// 三个队列各自为政,打印机无法协调
// 可能导致打印冲突、顺序混乱
}
问题:
class PrinterSpooler {
public:
static PrinterSpooler& getInstance() {
static PrinterSpooler instance;
return instance;
}
void addJob(const std::string& document) {
std::lock_guard<std::mutex> lock(mutex_);
jobQueue_.push(document);
std::cout << "添加打印任务:" << document << std::endl;
}
void processJobs() {
std::lock_guard<std::mutex> lock(mutex_);
while (!jobQueue_.empty()) {
std::cout << "正在打印:" << jobQueue_.front() << std::endl;
jobQueue_.pop();
}
}
private:
PrinterSpooler() = default;
std::queue<std::string> jobQueue_;
std::mutex mutex_;
};
int main() {
// 所有打印任务都通过唯一的队列管理
PrinterSpooler::getInstance().addJob("文档 A"); // 进入统一队列
PrinterSpooler::getInstance().addJob("文档 B"); // 同一个队列
PrinterSpooler::getInstance().addJob("文档 C"); // 同一个队列
PrinterSpooler::getInstance().processJobs(); // 按顺序打印
// 输出:文档 A → 文档 B → 文档 C(有序)
}
优点:
单例模式的核心价值是控制对共享资源的访问,以下是典型场景:
| 场景 | 单例对象 | 为什么需要单例 |
|---|---|---|
| 打印队列 | PrinterSpooler | 多实例会导致打印冲突和顺序混乱 |
| 数据库连接池 | ConnectionPool | 多实例会创建过多连接,耗尽数据库资源 |
| 线程池 | ThreadPool | 多实例会导致线程数失控,系统过载 |
| 配置管理 | ConfigManager | 多实例可能读取到不一致的配置 |
| 设备管理 | DeviceManager | 硬件设备天然唯一,必须统一管理 |
| 音视频播放器 | MediaPlayer | 播放状态和资源需要统一协调 |
class Singleton {
public:
static Singleton& getInstance() {
return instance;
}
void doSomething() {
std::cout << "Singleton working!" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton created!" << std::endl;
}
// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton instance; // 静态成员,程序启动时初始化
};
// 必须在类外 (.cpp) 定义静态成员
Singleton Singleton::instance;
程序启动 -> 静态成员 instance 初始化 -> 调用构造函数创建对象 -> 单例已就绪 -> 调用者调用 getInstance() 返回同一个 instance
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
// 线程 A 执行到这里
instance = new Singleton();
// 线程 B 也执行到这里
}
// 可能创建两个实例!
return instance;
}
private:
Singleton() = default;
static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;
问题:多线程环境下可能创建多个实例!
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
if (instance == nullptr) { // 第一次检查(无锁)
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) { // 第二次检查(有锁)
instance = new Singleton();
}
}
return *instance;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance;
static std::mutex mutex;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
否 -> instance == nullptr? -> 第一次检查 -> 获取互斥锁 -> instance == nullptr? -> 第二次检查 -> 释放锁 -> 创建实例 -> instance = new Singleton -> 释放锁 -> 返回单例
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 保证线程安全
return instance;
}
void doSomething() {
std::cout << "Singleton working!" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton created!" << std::endl;
}
~Singleton() {
std::cout << "Singleton destroyed!" << std::endl;
}
// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
C++11 标准规定:如果多个线程同时首次进入声明静态局部变量的语句,
只有一个线程会执行初始化,其他线程会等待初始化完成。
这是编译器层面的保证,无需手动加锁!
| 实现方式 | 线程安全 | 延迟加载 | 代码复杂度 | 推荐指数 |
|---|---|---|---|---|
| 饿汉式 | ✅ 是 | ❌ 否 | ⭐ 简单 | ⭐⭐⭐ |
| 懒汉式(无锁) | ❌ 否 | ✅ 是 | ⭐ 简单 | ⭐ |
| 双重检查锁 | ✅ 是 | ✅ 是 | ⭐⭐⭐ 复杂 | ⭐⭐⭐ |
| C++11 静态局部变量 | ✅ 是 | ✅ 是 | ⭐ 简单 | ⭐⭐⭐⭐⭐ |
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
// ========== 推荐的单例实现(C++11) ==========
class Singleton {
public:
// 获取唯一实例
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
// 示例方法
void showMessage(const std::string& msg) {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << msg << std::endl;
}
int getCount() const {
return count_;
}
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++count_;
}
private:
// 私有构造函数
Singleton() : count_(0) {
std::cout << "[单例] 构造函数被调用,实例已创建!" << std::endl;
}
// 私有析构函数
~Singleton() {
std::cout << "[单例] 析构函数被调用,实例已销毁!" << std::endl;
}
// 禁止拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 禁止移动
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
int count_;
mutable std::mutex mutex_;
};
// ========== 测试代码 ==========
void threadFunc(int threadId) {
// 每个线程都访问同一个单例
Singleton& singleton = Singleton::getInstance();
singleton.increment();
singleton.showMessage("线程 " + std::to_string(threadId) + " 访问单例");
}
int main() {
std::cout << "========== 单例模式测试 ==========" << std::endl;
std::cout << std::endl;
// 创建多个线程同时访问单例
std::vector<std::thread> threads;
std::cout << "[主线程] 创建 5 个线程..." << std::endl;
for (int i = 1; i <= 5; ++i) {
threads.emplace_back(threadFunc, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << std::endl;
std::cout << "[主线程] 所有线程完成" << std::endl;
std::cout << "[主线程] 单例被访问次数:" << Singleton::getInstance().getCount() << std::endl;
std::cout << std::endl;
std::cout << "========== 程序结束 ==========" << std::endl;
return 0;
}
========== 单例模式测试 ==========
[主线程] 创建 5 个线程...
[单例] 构造函数被调用,实例已创建!
线程 5 访问单例
线程 2 访问单例
线程 3 访问单例
线程 1 访问单例
线程 4 访问单例
[主线程] 所有线程完成
[主线程] 单例被访问次数:5
========== 程序结束 ==========
[单例] 析构函数被调用,实例已销毁!
// ❌ 存在内存泄漏风险
class Singleton {
public:
static Singleton* getInstance() {
if (!instance) {
instance = new Singleton(); // new 了但没有 delete
}
return instance;
}
private:
static Singleton* instance;
};
解决方案:
// ✅ 方案 1:使用静态局部变量(推荐)
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
// ✅ 方案 2:使用智能指针
static std::shared_ptr<Singleton> getInstance() {
static std::shared_ptr<Singleton> instance(new Singleton());
return instance;
}
class Counter {
public:
static Counter& getInstance() {
static Counter instance;
return instance;
}
// ❌ 非线程安全的操作
void increment() {
count_++; // 多线程同时调用会出问题
}
private:
int count_ = 0;
};
解决方案:
class Counter {
public:
static Counter& getInstance() {
static Counter instance;
return instance;
}
// ✅ 使用互斥锁保护
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
count_++;
}
// ✅ 或者使用原子变量
void incrementAtomic() {
atomicCount_++;
}
private:
int count_ = 0;
std::atomic<int> atomicCount_{0};
std::mutex mutex_;
};
// ❌ 单例类难以被继承
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
protected:
Singleton() = default;
};
class DerivedSingleton : public Singleton {
// 问题:无法创建 DerivedSingleton 的单例
};
解决方案:使用模板
// ✅ 单例模板(CRTP 模式)
template<typename T>
class Singleton {
public:
static T& getInstance() {
static T instance;
return instance;
}
protected:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 使用方式
class Logger : public Singleton<Logger> {
friend class Singleton<Logger>; // 允许基类访问私有构造
private:
Logger() = default;
public:
void log(const std::string& msg) {
std::cout << msg << std::endl;
}
};
// 调用
Logger::getInstance().log("Hello Singleton!");
1️⃣ 单例模式 = 私有构造 + 静态实例 + 公有访问点
2️⃣ C++11 静态局部变量是最佳实践,天然线程安全
3️⃣ 禁止拷贝和赋值,使用 = delete
4️⃣ 单例内部操作仍需考虑线程安全
5️⃣ 避免滥用单例,它本质上是一个全局变量
核心原则:当需要控制对共享资源的访问时,考虑使用单例模式。
| 场景类型 | 是否适用 | 说明 |
|---|---|---|
| 打印队列/任务队列 | ✅ 非常适合 | 物理资源天然唯一,必须统一调度 |
| 数据库连接池 | ✅ 非常适合 | 防止连接过多导致资源耗尽 |
| 线程池 | ✅ 非常适合 | 防止线程数失控导致系统崩溃 |
| 配置管理器 | ✅ 适合 | 保证配置一致性 |
| 播放器核心 | ✅ 适合 | 状态和资源统一管理 |
| 日志管理器 | ⚠️ 可用但非必需 | 更多是便利性而非必要性 |
| 频繁创建的对象 | ❌ 不适用 | 应使用对象池 |
| 需要多实例的场景 | ❌ 不适用 | 与单例矛盾 |
| 需要依赖注入 | ⚠️ 慎用 | 影响可测试性 |
// ✅ 推荐的单例写法
class MySingleton {
public:
static MySingleton& getInstance() {
static MySingleton instance;
return instance;
}
// 业务方法...
private:
MySingleton() = default;
~MySingleton() = default;
MySingleton(const MySingleton&) = delete; // 禁止拷贝
MySingleton& operator=(const MySingleton&) = delete; // 禁止赋值
MySingleton(MySingleton&&) = delete; // 禁止移动构造
MySingleton& operator=(MySingleton&&) = delete; // 禁止移动赋值
};

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online