<摘要>
单例模式是创建型设计模式中最经典且应用最广泛的设计模式之一,它确保一个类只有一个实例并提供全局访问点。本文从历史背景和核心概念出发,详细阐述了单例模式的产生背景和演进历程,深入剖析了其在资源管理、状态一致性和访问控制方面的设计意图。通过饿汉式、懒汉式、Meyer’s Singleton和线程安全双检锁等四种经典实现方式的对比分析,结合日志系统、配置管理、数据库连接池等实际应用场景,提供了完整的可编译运行代码示例和Makefile配置。文章特别针对多线程环境下的线程安全问题进行了深度解析,使用时序图和状态图直观演示了竞态条件的产生与解决方案,最后探讨了单例模式的测试策略和替代方案,为开发者提供了全面而实用的指导。


<解析>

1. 背景与核心概念

1.1 产生背景与发展脉络

单例模式(Singleton Pattern)的诞生源于软件开发中对特定类型对象管理的实际需求。在早期的软件开发实践中,开发者逐渐意识到某些类的实例应该在整个应用程序生命周期中只存在一个,这种需求催生了单例模式的形成。

历史演进阶段

  1. 初期探索阶段(1980年代前):在面向对象编程范式普及之前,开发者通常使用全局变量来实现类似单例的功能。这种方式虽然简单,但带来了命名冲突、初始化顺序不确定和访问控制缺失等问题。

  2. 模式化阶段(1980-1990年代):随着"Gang of Four"(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)在1994年出版的《设计模式:可复用面向对象软件的基础》一书中正式提出单例模式,它被系统性地归纳为23种经典设计模式之一,属于创建型模式类别。

  3. 语言特性融合阶段(2000年代至今):随着编程语言的发展,单例模式的实现方式不断演进。C++11标准引入的线程局部存储(thread_local)、原子操作(atomic)和静态变量初始化线程安全等特性,为单例模式提供了更优雅的实现方案。

产生的根本原因

  • 资源共享需求:如数据库连接池、线程池等需要集中管理的资源
  • 状态一致性要求:如配置管理、计数器等需要全局一致状态的对象
  • 性能优化考虑:避免频繁创建销毁重量级对象带来的开销
  • 访问控制需要:集中管控对特定资源的访问,如日志系统

1.2 核心概念与关键术语

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点来获取该实例的设计模式。

关键特性

  • 唯一实例性(Instance Uniqueness):保证类只有一个实例存在
  • 全局可访问性(Global Accessibility):提供统一的访问入口
  • 延迟初始化(Lazy Initialization):多数实现支持在第一次使用时创建实例
  • 线程安全性(Thread Safety):在多线程环境下保证正确性

基本结构组件

class Singleton {
private:static Singleton* instance;    // 静态私有成员,保存唯一实例Singleton();                   // 私有构造函数,防止外部实例化Singleton(const Singleton&) = delete;            // 删除拷贝构造函数Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符public:static Singleton* getInstance(); // 静态公共方法,提供全局访问点// 其他成员函数...
};

UML表示

Singleton
-static Singleton* instance
-Singleton()
+static getInstance()
+someOperation() : void

图1.1:单例模式基本UML类图

2. 设计意图与考量

2.1 核心设计目标

单例模式的设计旨在解决以下核心问题:

2.1.1 controlled Instance Creation(受控实例创建)
通过将构造函数设为私有,单例模式彻底消除了客户端随意创建类实例的可能性。这种强制性的创建控制确保了实例数量的严格管理,从语言机制层面而非仅仅约定层面保证了单一实例的约束。

2.1.2 Global Access Point(全局访问点)
提供静态方法getInstance()作为获取单例实例的统一入口,解决了全局变量方式的散乱访问问题。这种方法:

  • 明确了职责:清晰标识这是获取实例的正确方式
  • 封装了复杂性:隐藏了实例创建和管理的细节
  • 提供了灵活性:允许在不改变客户端代码的情况下修改实例化策略

2.1.3 Resource Coordination(资源协调)
对于需要协调共享资源的场景,单例模式提供了自然的设计方案:

  • 避免资源冲突:如多个日志写入器同时写文件可能导致的内容交错
  • 减少资源浪费:如数据库连接的重用而非重复创建
  • 统一管理策略:如缓存的一致性管理和过期策略

2.2 设计考量因素

2.2.1 线程安全性考量
在多线程环境下,单例模式的实现必须考虑竞争条件(Race Condition)问题:

Thread1Thread2Singleton竞态条件发生场景getInstance()检查instance==nullptrgetInstance()检查instance==nullptrnew Singleton()创建实例new Singleton()创建另一个实例两个线程得到不同实例,违反单例原则Thread1Thread2Singleton

图2.1:多线程环境下的竞态条件时序图

解决方案包括:

  • 饿汉式初始化:在程序启动时即创建实例,避免运行时竞争
  • 互斥锁保护:在懒汉式初始化时使用锁机制
  • 双检锁模式:减少锁的使用频率,提高性能
  • 局部静态变量:利用C++11的静态变量线程安全特性

2.2.2 初始化时机权衡

初始化方式优点缺点适用场景
饿汉式实现简单,线程安全可能提前占用资源,启动慢实例小且必定使用
懒汉式资源按需分配,启动快实现复杂,需要线程安全措施实例大或不一定会使用

2.2.3 继承与扩展性
单例类的继承会带来设计上的挑战:

  • 构造函数隐私性:派生类需要访问基类构造函数
  • 实例唯一性:每个派生类是否都应该是单例?
  • 模板方法应用:通过模板元编程实现可复用的单例基类

2.2.4 测试困难性
单例模式对单元测试不友好,主要原因:

  • 全局状态共享:测试用例之间可能相互影响
  • 难以模拟:无法轻松替换为模拟对象进行测试
  • 重置困难:需要额外机制在测试间重置单例状态

2.2.5 生命周期管理
单例实例的生命周期管理需要考虑:

  • 创建时机:何时以及如何创建实例
  • 销毁时机:是否需要显式销毁,如何保证安全销毁
  • 依赖关系:单例之间的依赖关系及初始化顺序

3. 实例与应用场景

3.1 日志系统(Logger)

应用场景
在大多数应用程序中,日志系统需要满足以下要求:

  • 全局唯一:多个模块共享同一个日志实例
  • 线程安全:多线程环境下能安全写入日志
  • 集中配置:统一设置日志级别、输出目标等

完整实现代码

// logger.h
#ifndef LOGGER_H
#define LOGGER_H#include <iostream>
#include <string>
#include <mutex>
#include <fstream>enum class LogLevel {DEBUG,INFO,WARNING,ERROR,CRITICAL
};class Logger {
public:static Logger& getInstance();void setLogLevel(LogLevel level);void setLogFile(const std::string& filename);void log(const std::string& message, LogLevel level = LogLevel::INFO);// 删除拷贝构造函数和赋值运算符Logger(const Logger&) = delete;Logger& operator=(const Logger&) = delete;private:Logger();~Logger();std::string getLevelString(LogLevel level) const;static Logger* instance;static std::mutex mutex;LogLevel currentLevel;std::ofstream logFile;std::ostream* outputStream;bool useFile;
};#endif // LOGGER_H
// logger.cpp
#include "logger.h"
#include <iostream>
#include <chrono>
#include <iomanip>Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;Logger::Logger() : currentLevel(LogLevel::INFO), useFile(false), outputStream(&std::cout) {std::cout << "Logger initialized" << std::endl;
}Logger::~Logger() {if (logFile.is_open()) {logFile.close();}std::cout << "Logger destroyed" << std::endl;
}Logger& Logger::getInstance() {std::lock_guard<std::mutex> lock(mutex);if (instance == nullptr) {instance = new Logger();}return *instance;
}void Logger::setLogLevel(LogLevel level) {currentLevel = level;
}void Logger::setLogFile(const std::string& filename) {std::lock_guard<std::mutex> lock(mutex);if (logFile.is_open()) {logFile.close();}logFile.open(filename, std::ios::out | std::ios::app);if (logFile.is_open()) {useFile = true;outputStream = &logFile;} else {useFile = false;outputStream = &std::cout;std::cerr << "Failed to open log file: " << filename << std::endl;}
}void Logger::log(const std::string& message, LogLevel level) {if (level < currentLevel) {return;}std::lock_guard<std::mutex> lock(mutex);// 获取当前时间auto now = std::chrono::system_clock::now();auto time = std::chrono::system_clock::to_time_t(now);*outputStream << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] "<< "[" << getLevelString(level) << "] "<< message << std::endl;
}std::string Logger::getLevelString(LogLevel level) const {switch (level) {case LogLevel::DEBUG: return "DEBUG";case LogLevel::INFO: return "INFO";case LogLevel::WARNING: return "WARNING";case LogLevel::ERROR: return "ERROR";case LogLevel::CRITICAL: return "CRITICAL";default: return "UNKNOWN";}
}
// main_logger.cpp
#include "logger.h"
#include <thread>
#include <vector>void logMessages(int threadId) {for (int i = 0; i < 5; ++i) {std::string message = "Thread " + std::to_string(threadId) + " - Message " + std::to_string(i);Logger::getInstance().log(message, LogLevel::INFO);std::this_thread::sleep_for(std::chrono::milliseconds(100));}
}int main() {// 设置日志级别和输出文件Logger::getInstance().setLogLevel(LogLevel::DEBUG);Logger::getInstance().setLogFile("application.log");Logger::getInstance().log("Application started", LogLevel::INFO);// 创建多个线程测试线程安全性std::vector<std::thread> threads;for (int i = 0; i < 3; ++i) {threads.emplace_back(logMessages, i);}for (auto& thread : threads) {thread.join();}Logger::getInstance().log("Application finished", LogLevel::INFO);return 0;
}

Makefile配置

# Makefile for Logger example
CXX = g++
CXXFLAGS = -std=c++11 -pthread -Wall -WextraTARGET = logger_example
SOURCES = main_logger.cpp logger.cpp
HEADERS = logger.h$(TARGET): $(SOURCES) $(HEADERS)$(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCES)clean:rm -f $(TARGET) application.logrun: $(TARGET)./$(TARGET).PHONY: clean run

编译运行

make        # 编译程序
make run    # 运行程序

3.2 配置管理器(Configuration Manager)

应用场景
应用程序通常需要读取和管理配置文件,配置管理器应该:

  • 全局唯一:确保所有模块使用相同的配置
  • 懒加载:只在第一次使用时加载配置
  • 线程安全:支持多线程并发读取配置

完整实现代码

// config_manager.h
#ifndef CONFIG_MANAGER_H
#define CONFIG_MANAGER_H#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>class ConfigManager {
public:static ConfigManager& getInstance();bool loadConfig(const std::string& filename);std::string getString(const std::string& key, const std::string& defaultValue = "") const;int getInt(const std::string& key, int defaultValue = 0) const;double getDouble(const std::string& key, double defaultValue = 0.0) const;bool getBool(const std::string& key, bool defaultValue = false) const;void setString(const std::string& key, const std::string& value);void setInt(const std::string& key, int value);void setDouble(const std::string& key, double value);void setBool(const std::string& key, bool value);bool saveConfig(const std::string& filename) const;// 删除拷贝构造函数和赋值运算符ConfigManager(const ConfigManager&) = delete;ConfigManager& operator=(const ConfigManager&) = delete;private:ConfigManager();~ConfigManager() = default;void parseLine(const std::string& line);void setDefaultValues();mutable std::mutex configMutex;std::unordered_map<std::string, std::string> configMap;std::string configFileName;
};#endif // CONFIG_MANAGER_H
// config_manager.cpp
#include "config_manager.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cctype>ConfigManager& ConfigManager::getInstance() {static ConfigManager instance;return instance;
}ConfigManager::ConfigManager() {setDefaultValues();
}void ConfigManager::setDefaultValues() {std::lock_guard<std::mutex> lock(configMutex);configMap["server.host"] = "localhost";configMap["server.port"] = "8080";configMap["database.enabled"] = "true";configMap["log.level"] = "info";configMap["cache.size"] = "100";
}bool ConfigManager::loadConfig(const std::string& filename) {std::ifstream file(filename);if (!file.is_open()) {std::cerr << "Failed to open config file: " << filename << std::endl;return false;}std::lock_guard<std::mutex> lock(configMutex);configFileName = filename;configMap.clear();setDefaultValues(); // 重新设置默认值std::string line;while (std::getline(file, line)) {// 移除行首尾的空白字符line.erase(0, line.find_first_not_of(" \t"));line.erase(line.find_last_not_of(" \t") + 1);// 跳过空行和注释if (line.empty() || line[0] == '#') {continue;}parseLine(line);}file.close();return true;
}void ConfigManager::parseLine(const std::string& line) {size_t equalsPos = line.find('=');if (equalsPos == std::string::npos) {return; // 无效行}std::string key = line.substr(0, equalsPos);std::string value = line.substr(equalsPos + 1);// 移除key和value首尾的空白字符key.erase(0, key.find_first_not_of(" \t"));key.erase(key.find_last_not_of(" \t") + 1);value.erase(0, value.find_first_not_of(" \t"));value.erase(value.find_last_not_of(" \t") + 1);// 移除value可能的引号if (!value.empty()) {if ((value.front() == '"' && value.back() == '"') ||(value.front() == '\'' && value.back() == '\'')) {value = value.substr(1, value.size() - 2);}}if (!key.empty()) {configMap[key] = value;}
}std::string ConfigManager::getString(const std::string& key, const std::string& defaultValue) const {std::lock_guard<std::mutex> lock(configMutex);auto it = configMap.find(key);return it != configMap.end() ? it->second : defaultValue;
}int ConfigManager::getInt(const std::string& key, int defaultValue) const {std::string value = getString(key, "");if (value.empty()) {return defaultValue;}try {return std::stoi(value);} catch (...) {return defaultValue;}
}double ConfigManager::getDouble(const std::string& key, double defaultValue) const {std::string value = getString(key, "");if (value.empty()) {return defaultValue;}try {return std::stod(value);} catch (...) {return defaultValue;}
}bool ConfigManager::getBool(const std::string& key, bool defaultValue) const {std::string value = getString(key, "");if (value.empty()) {return defaultValue;}// 转换为小写进行比较std::string lowerValue;std::transform(value.begin(), value.end(), std::back_inserter(lowerValue),[](unsigned char c) { return std::tolower(c); });if (lowerValue == "true" || lowerValue == "yes" || lowerValue == "1") {return true;} else if (lowerValue == "false" || lowerValue == "no" || lowerValue == "0") {return false;}return defaultValue;
}void ConfigManager::setString(const std::string& key, const std::string& value) {std::lock_guard<std::mutex> lock(configMutex);configMap[key] = value;
}void ConfigManager::setInt(const std::string& key, int value) {setString(key, std::to_string(value));
}void ConfigManager::setDouble(const std::string& key, double value) {setString(key, std::to_string(value));
}void ConfigManager::setBool(const std::string& key, bool value) {setString(key, value ? "true" : "false");
}bool ConfigManager::saveConfig(const std::string& filename) const {std::ofstream file(filename);if (!file.is_open()) {return false;}std::lock_guard<std::mutex> lock(configMutex);for (const auto& pair : configMap) {file << pair.first << " = " << pair.second << std::endl;}file.close();return true;
}
// main_config.cpp
#include "config_manager.h"
#include <iostream>
#include <thread>void printConfig(const std::string& threadName) {auto& config = ConfigManager::getInstance();std::cout << threadName << " - Server: " << config.getString("server.host") << ":" << config.getInt("server.port") << std::endl;std::cout << threadName << " - Database enabled: " << std::boolalpha << config.getBool("database.enabled") << std::endl;std::cout << threadName << " - Log level: " << config.getString("log.level") << std::endl;std::cout << threadName << " - Cache size: " << config.getInt("cache.size") << " MB" << std::endl;
}void configReader(int threadId) {std::string threadName = "Thread_" + std::to_string(threadId);printConfig(threadName);
}int main() {// 创建示例配置文件std::ofstream configFile("app.conf");configFile << "# Application configuration\n";configFile << "server.host = 192.168.1.100\n";configFile << "server.port = 9090\n";configFile << "database.enabled = true\n";configFile << "log.level = debug\n";configFile << "cache.size = 256\n";configFile.close();// 加载配置文件if (!ConfigManager::getInstance().loadConfig("app.conf")) {std::cerr << "Failed to load configuration" << std::endl;return 1;}std::cout << "Main thread - Configuration loaded successfully\n";// 创建多个线程读取配置std::thread threads[3];for (int i = 0; i < 3; ++i) {threads[i] = std::thread(configReader, i);}// 修改一些配置值ConfigManager::getInstance().setString("server.host", "10.0.0.1");ConfigManager::getInstance().setInt("server.port", 8080);for (int i = 0; i < 3; ++i) {threads[i].join();}// 保存当前配置if (ConfigManager::getInstance().saveConfig("app_modified.conf")) {std::cout << "Configuration saved to app_modified.conf" << std::endl;}return 0;
}

Makefile配置

# Makefile for Config Manager example
CXX = g++
CXXFLAGS = -std=c++11 -pthread -Wall -WextraTARGET = config_example
SOURCES = main_config.cpp config_manager.cpp
HEADERS = config_manager.h$(TARGET): $(SOURCES) $(HEADERS)$(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCES)clean:rm -f $(TARGET) app.conf app_modified.confrun: $(TARGET)./$(TARGET).PHONY: clean run

编译运行

make        # 编译程序
make run    # 运行程序

3.3 数据库连接池(Database Connection Pool)

应用场景
数据库连接是昂贵的资源,连接池需要:

  • 限制连接数量:防止过多连接耗尽数据库资源
  • 重用连接:避免频繁创建和关闭连接
  • 全局管理:所有数据库操作共享同一个连接池

完整实现代码

// connection_pool.h
#ifndef CONNECTION_POOL_H
#define CONNECTION_POOL_H#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <string>
#include <functional>// 模拟数据库连接类
class DatabaseConnection {
public:DatabaseConnection(const std::string& connectionString) : connectionString(connectionString), connected(false) {connect();}~DatabaseConnection() {disconnect();}bool execute(const std::string& query) {if (!connected) {return false;}// 模拟查询执行std::cout << "Executing query: " << query << std::endl;return true;}bool isConnected() const {return connected;}void disconnect() {if (connected) {std::cout << "Disconnecting from database" << std::endl;connected = false;}}private:bool connect() {std::cout << "Connecting to database: " << connectionString << std::endl;// 模拟连接耗时std::this_thread::sleep_for(std::chrono::milliseconds(100));connected = true;return true;}std::string connectionString;bool connected;
};class ConnectionPool {
public:static ConnectionPool& getInstance();void initialize(size_t maxSize, const std::string& connectionString);std::shared_ptr<DatabaseConnection> getConnection();void returnConnection(DatabaseConnection* connection);size_t availableCount() const;size_t inUseCount() const;// 删除拷贝构造函数和赋值运算符ConnectionPool(const ConnectionPool&) = delete;ConnectionPool& operator=(const ConnectionPool&) = delete;private:ConnectionPool();~ConnectionPool();mutable std::mutex poolMutex;std::condition_variable connectionAvailable;std::queue<DatabaseConnection*> availableConnections;std::vector<DatabaseConnection*> allConnections;size_t maxPoolSize;std::string connectionString;bool initialized;
};#endif // CONNECTION_POOL_H
// connection_pool.cpp
#include "connection_pool.h"
#include <iostream>
#include <algorithm>ConnectionPool& ConnectionPool::getInstance() {static ConnectionPool instance;return instance;
}ConnectionPool::ConnectionPool() : maxPoolSize(0), initialized(false) {
}ConnectionPool::~ConnectionPool() {std::lock_guard<std::mutex> lock(poolMutex);for (auto conn : allConnections) {delete conn;}allConnections.clear();while (!availableConnections.empty()) {availableConnections.pop();}
}void ConnectionPool::initialize(size_t maxSize, const std::string& connString) {std::lock_guard<std::mutex> lock(poolMutex);if (initialized) {return;}maxPoolSize = maxSize;connectionString = connString;initialized = true;// 预先创建一些连接for (size_t i = 0; i < std::min(size_t(2), maxSize); ++i) {auto conn = new DatabaseConnection(connectionString);availableConnections.push(conn);allConnections.push_back(conn);}
}std::shared_ptr<DatabaseConnection> ConnectionPool::getConnection() {std::unique_lock<std::mutex> lock(poolMutex);if (!initialized) {throw std::runtime_error("Connection pool not initialized");}// 等待可用连接connectionAvailable.wait(lock, [this]() {return !availableConnections.empty() || allConnections.size() < maxPoolSize;});if (!availableConnections.empty()) {auto conn = availableConnections.front();availableConnections.pop();return std::shared_ptr<DatabaseConnection>(conn, [this](DatabaseConnection* conn) { returnConnection(conn); });}if (allConnections.size() < maxPoolSize) {auto conn = new DatabaseConnection(connectionString);allConnections.push_back(conn);return std::shared_ptr<DatabaseConnection>(conn, [this](DatabaseConnection* conn) { returnConnection(conn); });}throw std::runtime_error("Failed to get database connection");
}void ConnectionPool::returnConnection(DatabaseConnection* connection) {{std::lock_guard<std::mutex> lock(poolMutex);if (connection->isConnected()) {availableConnections.push(connection);} else {// 移除无效连接auto it = std::find(allConnections.begin(), allConnections.end(), connection);if (it != allConnections.end()) {allConnections.erase(it);}delete connection;}}connectionAvailable.notify_one();
}size_t ConnectionPool::availableCount() const {std::lock_guard<std::mutex> lock(poolMutex);return availableConnections.size();
}size_t ConnectionPool::inUseCount() const {std::lock_guard<std::mutex> lock(poolMutex);return allConnections.size() - availableConnections.size();
}
// main_connection_pool.cpp
#include "connection_pool.h"
#include <iostream>
#include <thread>
#include <vector>void databaseTask(int taskId) {try {auto& pool = ConnectionPool::getInstance();auto connection = pool.getConnection();std::cout << "Task " << taskId << " got connection. "<< "Available: " << pool.availableCount() << ", In use: " << pool.inUseCount() << std::endl;// 模拟数据库操作connection->execute("SELECT * FROM users WHERE id = " + std::to_string(taskId));std::this_thread::sleep_for(std::chrono::milliseconds(200));std::cout << "Task " << taskId << " releasing connection" << std::endl;} catch (const std::exception& e) {std::cerr << "Task " << taskId << " failed: " << e.what() << std::endl;}
}int main() {// 初始化连接池ConnectionPool::getInstance().initialize(5, "host=localhost;dbname=test;user=root");std::cout << "Connection pool initialized. Max size: 5" << std::endl;std::cout << "Initial available connections: " << ConnectionPool::getInstance().availableCount() << std::endl;// 创建多个任务并发使用连接std::vector<std::thread> tasks;for (int i = 0; i < 10; ++i) {tasks.emplace_back(databaseTask, i);std::this_thread::sleep_for(std::chrono::milliseconds(50));}for (auto& task : tasks) {task.join();}std::cout << "All tasks completed." << std::endl;std::cout << "Final available connections: " << ConnectionPool::getInstance().availableCount() << std::endl;return 0;
}

Makefile配置

# Makefile for Connection Pool example
CXX = g++
CXXFLAGS = -std=c++11 -pthread -Wall -WextraTARGET = connection_pool_example
SOURCES = main_connection_pool.cpp connection_pool.cpp
HEADERS = connection_pool.h$(TARGET): $(SOURCES) $(HEADERS)$(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCES)clean:rm -f $(TARGET)run: $(TARGET)./$(TARGET).PHONY: clean run

编译运行

make        # 编译程序
make run    # 运行程序

4. 交互性内容解析

4.1 多线程环境下的交互分析

单例模式在多线程环境下的行为复杂性主要体现在实例化过程中。以下通过时序图详细分析不同实现方式的线程交互:

4.1.1 不安全懒汉式的竞态条件

ThreadAThreadBSingletonClass不安全懒汉式初始化getInstance()检查instance==nullptr确认instance为nullptrgetInstance()检查instance==nullptr确认instance为nullptr(此时还未创建)new Singleton()创建实例赋值给instancenew Singleton()创建另一个实例赋值给instance(覆盖前一个)产生两个实例,内存泄漏ThreadAThreadBSingletonClass

4.1.2 双检锁模式的正确交互

ThreadAThreadBMutexSingletonClassgetInstance()检查instance==nullptr确认instance为nullptrlock()获取互斥锁getInstance()检查instance==nullptr等待(因为instance仍为nullptr)再次检查instance==nullptr(双检)new Singleton()创建实例原子操作赋值给instanceunlock()释放互斥锁lock()获取互斥锁再次检查instance≠nullptr(双检)unlock()释放互斥锁返回已创建的实例ThreadAThreadBMutexSingletonClass

4.2 单例与依赖组件的交互

在实际应用中,单例对象往往需要与其他系统组件进行交互。以下以日志单例为例展示其与文件系统、网络服务的交互:

ClientLoggerSingletonFileSystemNetworkServicelog("Error occurred", ERROR)获取当前时间戳格式化日志消息write(log_file, message)写入成功sendLog(message)发送确认alt[网络日志启用]日志记录完成ClientLoggerSingletonFileSystemNetworkService

5. 总结与最佳实践

5.1 单例模式适用场景总结

单例模式在以下场景中特别有用:

  1. 资源共享场景:如数据库连接池、线程池等需要集中管理的资源
  2. 配置管理:应用程序的全局配置信息管理
  3. 日志记录:统一的日志记录系统
  4. 缓存系统:全局缓存管理
  5. 设备访问:如打印机后台处理服务

5.2 实现方式选择指南

实现方式优点缺点适用场景
饿汉式线程安全,实现简单可能浪费资源,启动慢实例小且必定使用
懒汉式(带锁)资源按需分配每次访问都需要加锁,性能差不频繁访问的单例
双检锁线程安全,性能较好实现复杂,需要C++11支持高性能要求的场景
Meyer’s Singleton简单,线程安全,自动销毁C++11以上支持现代C++项目首选

5.3 最佳实践建议

  1. 优先使用Meyer’s Singleton:在C++11及以上环境中,这是最简单安全的实现方式
  2. 考虑依赖注入:对于可测试性要求高的项目,考虑使用依赖注入替代单例
  3. 谨慎使用单例:单例本质上是全局状态,过度使用会导致代码耦合度高
  4. 注意销毁顺序:单例的销毁顺序可能影响其他静态对象的析构
  5. 提供重置机制:在测试环境中,提供重置单例状态的方法

5.4 现代C++改进

C++11及以上版本提供了更好的单例实现工具:

  • std::call_once:保证一次性初始化
  • thread_local:实现线程局部单例
  • 原子操作:实现无锁或低锁同步
  • 静态局部变量:线程安全的延迟初始化

单例模式是强大的工具,但需要谨慎使用。正确应用时,它可以提供优雅的解决方案;滥用时,它会导致代码难以维护和测试。始终考虑是否真的需要单例,或者是否有更好的替代设计方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921964.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921964.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921964.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将GitHub远程仓库修改为ssh

8 将GitHub远程仓库修改为ssh 文章目录8 将GitHub远程仓库修改为ssh1 创建本地的ssh密钥2 设置GitHub密钥3 将本地库链接到远程仓库很多时候在使用GitHub的远程链接使用的是http的格式&#xff0c;但是这个格式并不好&#xff0c;尤其是在代码上传的时候&#xff0c;因此需要采…

【OEC-Turbo】网心云 OEC-Turbo 刷机 Armbian 系统教程

前言 大量网心云 OEC 及 OEC-Turbo 设备流入二手市场&#xff08;如海鲜市场&#xff09;&#xff0c;价格低至 70-100 元。相比同配置的拾光坞 N3&#xff08;约 380 元&#xff09;&#xff0c;OEC-Turbo 仅需一个零头&#xff0c;性价比极高。这些“矿渣”设备外观与玩客云…

25.线程概念和控制(二)

一、线程周边问题1.线程的优点创建一个新线程的代价要比创建一个新进程小得多。线程占用的资源要比进程少很多。能充分利用多处理器的可并行数量。在等待慢速I/O操作结束的同时&#xff0c;程序可执行其他的计算任务。计算密集型应用&#xff0c;为了能在多处理器系统上运行&am…

【CVPR2023】奔跑而非行走:追求更高FLOPS以实现更快神经网络

文章目录一、论文信息二、论文概要三、实验动机四、创新之处五、实验分析六、核心代码注释版本七、实验总结一、论文信息 论文题目&#xff1a;Run, Don’t Walk: Chasing Higher FLOPS for Faster Neural Networks中文题目&#xff1a;奔跑而非行走&#xff1a;追求更高FLOPS…

JVM(二)--- 类加载子系统

目录 前言 一、类加载过程 1. loading阶段 2. Linking阶段 2.1 验证 2.2 准备 2.3 解析 3. Initialization阶段 二、类加载器 1. 类加载器的分类 2. 用户自定义类加载器 三、双亲委派机制 四、其他知识点 前言 JVM的内存结构如图所示&#xff1a; 一、类加载过程…

Docker 容器的使用

1.容器的基本信息[roothost1 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9ac8245b5b08 img-layers-test "python /app/app.py" 45 hours ago Exited (0) 45 hour…

LLMs之Hallucinate:《Why Language Models Hallucinate》的翻译与解读

LLMs之Hallucinate&#xff1a;《Why Language Models Hallucinate》的翻译与解读 导读&#xff1a;该论文深入分析了语言模型中幻觉现象的成因&#xff0c;认为幻觉源于预训练阶段的统计压力和后训练阶段评估体系对猜测行为的奖励。论文提出了通过修改评估方法&#xff0c;使其…

Spring Cloud @RefreshScope 作用是什么?

RefreshScope 是 Spring Cloud 中的一个重要注解&#xff0c;主要作用如下&#xff1a; 主要功能动态刷新配置 使 Bean 能够在运行时动态刷新配置属性当配置中心的配置发生变化时&#xff0c;无需重启应用即可生效作用域管理 为 Bean 创建一个特殊的作用域 refresh标记的 Bean …

Flutter SDK 安装与国内镜像配置全流程(Windows / macOS / Linux)

这是一份面向国内网络环境的 Flutter 从零到可运行指引&#xff1a;覆盖 SDK 安装、平台依赖准备、国内镜像配置&#xff08;PUB_HOSTED_URL、FLUTTER_STORAGE_BASE_URL&#xff09;、Android 侧 Gradle 仓库加速&#xff0c;以及 Java/Gradle 版本兼容的关键坑位与排查思路。文…

【Java】NIO 简单介绍

简介 从 Java 1.4 版本开始引入的一个新的 I/O API&#xff0c;可以替代标准的 Java I/O。提供了与标准 I/O 不同的工作方式&#xff0c;核心是 通道&#xff08;Channel&#xff09;、缓冲区&#xff08;Buffer&#xff09; 和 选择器&#xff08;Selector&#xff09;。支持非…

Java爬虫获取京东item_get_app数据的实战指南

一、引言京东开放平台提供了丰富的API接口&#xff0c;其中item_get_app接口可用于获取商品的详细信息。这些数据对于市场分析、价格监控、商品推荐等场景具有重要价值。本文将详细介绍如何使用Java编写爬虫&#xff0c;通过调用京东开放平台的item_get_app接口获取商品详情数据…

Vue3源码reactivity响应式篇之批量更新

概述 在vue3响应式系统设计中&#xff0c;批量更新是优化性能的核心机制之一。当短时间内频繁多次修改响应式数据时&#xff0c;批量更新可以避免频繁触发订阅者的更新操作&#xff0c;将这些更新操作合并为一次&#xff0c;从而减少不必要的计算和DOM操作。 批量更新也是利用链…

AI 模型训练过程中参数用BF16转向FP16的原因

大模型训练从 FP16 转向 BF16 是一个关键的技术演进&#xff0c;其核心原因在于 BF16 在动态范围和精度之间取得了更优的平衡&#xff0c;从而极大地提升了训练&#xff08;尤其是大模型训练&#xff09;的稳定性和有效性。 1. 背景 为什么需要半精度浮点数 (FP16)&#xff1f;…

python网络爬取个人学习指南-(五)

**************************************************************************************************************author&#xff1a;keyinfodate&#xff1a;2025-09-09 23:50title&#xff1a;网络信息爬取之多联级标题内容点击****************************************…

RAG - 检索增强生成

第一部分&#xff1a;RAG 详解一、RAG 是什么&#xff1f;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种将信息检索&#xff08;或知识检索&#xff09;与大语言模型&#xff08;LLM&#xff09;的生成能力相结合的技术框架。它的…

大数据毕业设计选题推荐-基于大数据的分化型甲状腺癌复发数据可视化分析系统-Spark-Hadoop-Bigdata

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Spring Bean扫描

好的&#xff0c;没有问题。基于我们之前讨论的内容&#xff0c;这是一篇关于 Spring Bean 扫描问题的深度解析博客。Spring Bean扫描作者&#xff1a;Gz | 发布时间&#xff1a;2025年9月9日&#x1f3af; Spring如何找到你的Bean&#xff1f; 首先要理解原理。Spring的组件扫…

【 运维相关】-- HTTP 压测/负载发生器之新秀 oha

目录 oha 项目分析&#xff08;hatoo/oha&#xff09; 一、概述 二、安装 三、快速上手 三、常用参数&#xff08;摘选&#xff09; 四、输出解读&#xff08;终端 TUI&#xff09; 五、与其它工具对比 六、最佳实践 七、注意事项 八、参考 oha 项目分析&#xff08;h…

淘宝闪购基于FlinkPaimon的Lakehouse生产实践:从实时数仓到湖仓一体化的演进之路

摘要&#xff1a;本文整理自淘宝闪购(饿了么)大数据架构师王沛斌老师在 Flink Forward Asia 2025 城市巡回上海站的分享。引言在数字化转型的浪潮中&#xff0c;企业对实时数据处理的需求日益增长。传统的实时数仓架构在面对业务快速变化和数据规模爆炸性增长时&#xff0c;逐渐…

Android应用添加日历提醒功能

功能 在安卓应用里调用系统日历&#xff0c;直接创建一个带提醒的日历事件&#xff0c;甚至不需要跳转到日历界面&#xff0c;只需要获取系统日历的读取权限即可。 需要的权限 在AndroidManifest.xml里添加 <uses-permission android:name"android.permission.READ_CAL…