文章目錄
- “天上天下,唯我獨尊”——單例模式
- 單例模式簡介
- 單例模式結構
- 餓漢式
- 懶漢式
- 客戶端示例
- 運行結果
- 單例模式總結
- 構建型模式 Creational Patterns 小結 Summary
代碼倉庫

“天上天下,唯我獨尊”——單例模式
你能在電腦上調出兩個Windows任務管理器嗎?
假設能,如果兩個管理器顯示的數據相同,那何必要存在兩個呢?
如果兩個管理器顯示的數據不同,那我該相信哪一個呢?
試試看,應該有且僅有一個吧?一個系統里有且僅有一個Windows任務管理器實例供外界訪問 。如何保證系統里有且僅有一個實例對象呢?并且能夠供外界訪問?你可以在系統里定義一個統一的全局變量,但這并不能防止創建多個對象(想一想,為什么?)這就是單例模式的典型應用。
對于一個軟件系統中的某些類來說,只有一個實例很重要。假設Windows系統上可以同時調出兩個Windows任務管理器,這兩個任務管理器顯示的都是同樣的信息,那勢必造成內存資源的浪費;如果這兩個任務管理器顯示的是不同的信息,這也給用戶帶來了困惑,到底哪一個才是真實的狀態?
單例模式簡介
單例模式定義:
確保一個類只有一個實例,并提供一個全局訪問點來訪問這個唯一實例。
單例模式結構
單例模式結構非常簡單,其UML圖如下所示,只包含一個類,即單例類。為防止創建多個對象,其構造函數必須是私有的(外界不能訪問)。另一方面,為了提供一個全局訪問點來訪問該唯一實例,單例類提供了一個公有方法getInstance來返回該實例。
餓漢式
餓漢式:變量在聲明時便初始化。
// 餓漢式(立即加載)
// 餓漢式(Hungry Singleton):程序啟動時立即創建對象
class Singleton_Hungry {
public:static Singleton_Hungry* getInstance() {std::cout << "\n[Hungry] 獲取單例實例" << std::endl;static Singleton_Hungry instance; // 推薦方法,更安全更現代化return &instance;}void doSomething() const {std::cout << "\n[Hungry] 正在執行任務..." << std::endl;}private:// 禁止外界創建新的實例(私有構造、刪除拷貝構造和拷貝賦值)。Singleton_Hungry() {std::cout << "\n[Hungry] 構造函數調用" << std::endl;}~Singleton_Hungry() {std::cout << "\n[Hungry] 析構函數調用" << std::endl;}Singleton_Hungry(const Singleton_Hungry&) = delete;Singleton_Hungry& operator=(const Singleton_Hungry&) = delete;// static Singleton_Hungry* instance;
};// 靜態成員初始化 ??頭文件定義靜態變量,錯誤!
// 根本原因:違反了單一定義規則(One Definition Rule, ODR)
// 你在頭文件中定義了一個靜態變量,頭文件會被多個.cpp文件包含,這就導致在每個.cpp文件中都定義了一遍,最終會導致鏈接時的沖突,出現重復定義(multiple definition)錯誤。
// Singleton_Hungry* Singleton_Hungry::instance = new Singleton_Hungry();
可以看到,我們將構造方法定義為 private,這就保證了其他類無法實例化此類,必須通過 getInstance 方法才能獲取到唯一的 instance 實例,非常直觀。但餓漢式有一個弊端,那就是即使這個單例不需要使用,它也會在類加載之后立即創建出來,占用一塊內存,并增加類初始化時間。就好比一個電工在修理燈泡時,先把所有工具拿出來,不管是不是所有的工具都用得上。就像一個饑不擇食的餓漢,所以稱之為餓漢式。
懶漢式
懶漢式:先聲明一個空變量,需要用時才初始化。例如:
我們先聲明了一個 instance 變量,當需要使用時判斷此變量是否已被初始化,沒有初始化的話才 new 一個實例出來。就好比電工在修理燈泡時,開始比較偷懶,什么工具都不拿,當發現需要使用螺絲刀時,才把螺絲刀拿出來。當需要用鉗子時,再把鉗子拿出來。就像一個不到萬不得已不會行動的懶漢,所以稱之為懶漢式。
懶漢式解決了餓漢式的弊端,好處是按需加載,避免了內存浪費,減少了類初始化時間。
Singleton.h
// 確保一個類只有一個實例。
// 提供全局訪問點,讓用戶方便訪問// 懶漢式(線程安全,推薦寫法)
// 懶漢式(Lazy Singleton):延遲創建對象(用時才創建)
class Singleton_Lazy {
public:// 第一次 調用時,執行內部的 lambda 表達式,創建一個新的單例對象。// 后續所有次 調用時,都直接跳過這個 lambda,不再執行創建對象的操作。static Singleton_Lazy* getInstance() {// template< class Callable, class... Args >// void call_once(std::once_flag& flag, Callable&& f, Args&&... args);// 確保給定的**可調用對象(如lambda表達式)**僅被調用一次。// 如果多個線程同時執行該行代碼,也能確保只有一個線程實際執行了初始化操作,其他線程則會等待,直到第一次調用完成后再繼續執行,且不會重復執行初始化操作。// flag:一個標志位,標識對應的初始化是否已經完成。// Callable:一個可調用對象(比如lambda表達式或函數),代表真正要執行的初始化動作。std::call_once(initFlag, []() {std::cout << "\n[Lazy] 創建新的實例" << std::endl;// instance = std::make_unique<Singleton_Lazy>(); // ? 更安全,更推薦instance.reset(new Singleton_Lazy());});std::cout << "\n[Lazy] 獲取單例實例" << std::endl;return instance.get();}void doSomething() const {std::cout << "\n[Lazy] 正在執行任務..." << std::endl;}private:Singleton_Lazy() {std::cout << "\n[Lazy] 構造函數調用" << std::endl;}~Singleton_Lazy() {std::cout << "\n[Lazy] 析構函數調用" << std::endl;}// 拷貝構造函數(Copy Constructor)// 當使用一個已存在對象初始化另一個新對象時,調用拷貝構造函數Singleton_Lazy(const Singleton_Lazy&) = delete;// 拷貝賦值運算符(Copy Assignment Operator)// 當使用一個已存在的對象去給另一個已存在的對象賦值時調用。Singleton_Lazy& operator=(const Singleton_Lazy&) = delete;// ??一定要保留這兩個聲明!static std::unique_ptr<Singleton_Lazy> instance;static std::once_flag initFlag;// 🔑加上這一句:// 允許 std::unique_ptr 調用私有析構函數friend class std::default_delete<Singleton_Lazy>;
};
Singleton.cpp
#include "Singleton.h"std::unique_ptr<Singleton_Lazy> Singleton_Lazy::instance = nullptr;
std::once_flag Singleton_Lazy::initFlag;
客戶端示例
#define THREAD_NUM 6
#include <iostream>
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "Singleton.h"pid_t GetThreadId() {// syscall 是一個系統調用接口,可以讓你直接調用操作系統提供的底層功能。// SYS_gettid 是 Linux 系統調用號,表示獲取當前線程的線程ID(gettid)。// syscall(SYS_gettid) 實際上是執行 gettid() 系統調用的操作,返回當前線程的線程ID。// 該調用返回當前線程的線程ID,通常與 pthread_self() 的返回值相同,但是 gettid 是返回內核級線程ID,而 pthread_self() 返回的是 POSIX 線程庫級別的線程ID// SYS_gettid 是一個常量,表示獲取當前線程ID的系統調用號。// 每個系統調用都有一個唯一的編號(常量),用于標識該系統調用。SYS_gettid 對應的是獲取線程ID的操作。return syscall(SYS_gettid);
}void* callSingleton_Lazy(void* arg) {int threadID = *(int*)arg;Singleton_Lazy *s = Singleton_Lazy::getInstance();printf("[Lazy] 線程編號: %d, 實例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 線程編號: %d, 實例地址: %p\n", threadID, s);return 0;
}void* callSingleton_Hungry(void* arg) {// 將arg 從 void* 類型的通用指針強制轉換成 int*類型的指針, 然后對轉換后的指針解引用,取出實際的整型數值(即線程編號)。int threadID = *(int*)arg;Singleton_Hungry *s = Singleton_Hungry::getInstance();printf("[Hungry] 線程編號: %d, 實例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 線程編號: %d, 實例地址: %p\n", threadID, s);return 0;
}int main() {pthread_t threads_pool[THREAD_NUM];int tids[THREAD_NUM], params[THREAD_NUM];for(int i = 0; i < THREAD_NUM; i++) {params[i] = i; // 獨立參數,避免競爭/*int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void *),void *restrict arg);*/// 前半部分線程調用懶漢式單例if(i < THREAD_NUM / 2)tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Lazy, (void*)¶ms[i]);else // 后半部分線程調用餓漢式單例tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Hungry, (void*)¶ms[i]);// On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.if(tids[i]) {printf("Error: unable to create thread.\n");exit(-1);}}for(int i = 0; i < THREAD_NUM; i++) {// On success, pthread_join() returns 0; on error, it returns an error numbertids[i] = pthread_join(threads_pool[i], NULL);if(tids[i]) {printf("Error: unable to join thread.\n");exit(-1);}}printf("main exiting.\n");return 0;
}
運行結果
單例模式總結
單例模式讓一個類同時負責了『業務功能』和『自身的創建與生命周期管理』兩個職責。
構建型模式 Creational Patterns 小結 Summary
之后我會持續更新,如果喜歡我的文章,請記得一鍵三連哦,點贊關注收藏,你的每一個贊每一份關注每一次收藏都將是我前進路上的無限動力 !!!↖(▔▽▔)↗感謝支持!