設計模式(C++)詳解—單例模式(1)

<摘要>
單例模式是創建型設計模式中最經典且應用最廣泛的設計模式之一,它確保一個類只有一個實例并提供全局訪問點。本文從歷史背景和核心概念出發,詳細闡述了單例模式的產生背景和演進歷程,深入剖析了其在資源管理、狀態一致性和訪問控制方面的設計意圖。通過餓漢式、懶漢式、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…