C++学习之路: 单例模板
引言:
1.單例模式的目的:確保一個類只有一個實 例,并提供對該實例的全局訪問。
2.?單例模式也稱為單件模式、單子模式,可能是使用最廣泛的設計模式。其意圖是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。
單例模式 如何實現只有一個實例?? 禁用拷貝構造函數,防止拷貝。
那么還可以通過顯式定義來 定義多個實例。
如 ?NonCopyable a, b; 雖然禁止拷貝,但是可以定義多個對象。
所以我們把構造函數也設為私有,這樣就不能通過顯式定義多個對象了。
?
那么如果把構造函數設為私有, 該如何實例化這個類呢? 我們之前不是學過 如果把構造函數設為私有, 這個便不可使用,那么不就沒有意義了嗎?
該如何解決這個問題呢? ?采用曲線救國的方式:在類內部定義一個static 全局函數, 讓它來替我們調用構造函數, 因為該函數在類的內部, 所以可以訪問私有的 構造函數,又因為是static函數, 所以也可以再類外部直接調用。
?
既然static靜態函數在外部直接調用, 那么是否可以定義多個對象呢?
例如 1.getInstrance()
? ? ? ?2.getInstrance() ? ? 這樣調用兩次不就等于創建兩個對象了嗎??
假如 類內部定義一個Singleton*pInstance_, 一個指向自身類的 對象指針那么我們便可以通過
static?getInstrance()來動態創建一個自身類 的對象了。
{ static pInstance_ = new Singleton;?} 如此便實現了我們的要求了。
但是也帶來了問題, 我們多次調用getInstance()便可以創建多個對象,
如果pInstance_ 設置為非static, 在實例化為一個對象之前, 它是不存在的,想想 如果我們沒有定義一個string s,
不存在對象,就不存在pInstance_,那么如何調用里面的成員呢? ? 答案是 static 成員, 不管這個是否存在一個對象, 我們都可以訪問static成員。 static成員的存在 不依賴對象。 只有該類存在, 該指針就存在。
單例模式需要解決的問題:
?
a)????? 把構造函數設為私有,禁用賦值和復制。帶來的問題:main中無法隨意生成對象
b)????? 提供一個static函數繞過構造函數為private的限制。問題:對象不唯一。
c)????? 設置一個static指針,每次先判斷是否為NULL(防止重復new出新的對象導致內存泄露)。此時實現了一個簡單的單例模式。但是此時在多線程環境下不唯一。
?
1. 如何構造不可復制的類。
1 #include <iostream> 2 #include "Thread.h" 3 #include <stdlib.h> 4 using namespace std; 5 6 //多線程下具有隱患 7 class Singleton 8 { 9 public: 10 static Singleton *getInstance() 11 { 12 if(pInstance_ == NULL) //線程的切換 13 { 14 ::sleep(1); 15 pInstance_ = new Singleton; 16 } 17 18 return pInstance_; 19 } 20 private: 21 Singleton() { } 22 23 static Singleton *pInstance_; //靜態成員可以再外部直接調用 24 }; 25 26 Singleton *Singleton::pInstance_ = NULL; //這一行并不是調用,而是定義, 如果在main中這樣寫顯然是不行的,因為pInstance_是一個私有成員 27 28 29 class TestThread : public Thread 30 { 31 public: 32 void run() 33 { 34 cout << Singleton::getInstance() << endl; 35 cout << Singleton::getInstance() << endl; 36 } 37 }; 38 39 int main(int argc, char const *argv[]) 40 { 41 //Singleton s; ERROR 42 43 44 //測試證明了多線程下本代碼存在競爭問題 45 46 TestThread threads[12]; 47 for(int ix = 0; ix != 12; ++ix) 48 { 49 threads[ix].start(); 50 } 51 52 for(int ix = 0; ix != 12; ++ix) 53 { 54 threads[ix].join(); 55 } 56 return 0; 57 }?
1 0xb1300468 2 0xb1300498 3 0x9f88728 4 0xb1300498 5 0xb1300478 6 0xb1300498 7 0xb1100488 8 0xb1300498 9 0xb1300488 10 0xb1300498 11 0xb1300498 12 0xb1300498 13 0x9f88738 14 0xb1300498 15 0x9f88748 16 0xb1300498 17 0xb1100478 18 0xb1300498 19 0xb1100498 20 0xb1300498 21 0xb1100468 22 0xb1300498 23 0xb11004a8 24 0xb11004a8多線程條件下打印出的結果。 多個對象其地址不同, 說明多線程new出了多個 對象,導致內存泄露。
?
?
上述代碼在多線程下回出現明顯的 bug, ? 如果兩個線程同時運行getInstance_ ,且同時進入判斷 pInstance_ 為空, 導致new 出多個Singleton對象, 但是pInstance_ 只能指向其中一個, 那么就導致了內存的泄露。
?
2. 為了解決線程同時訪問pInstance_的問題, 我們給它加上互斥鎖mutex;
?
1 #include <iostream> 2 #include "MutexLock.h" 3 #include "Thread.h" 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include <sys/time.h> 8 using namespace std; 9 10 int64_t getUTime(); 11 12 //多線程下具有隱患 13 class Singleton 14 { 15 public: 16 static Singleton *getInstance() 17 { 18 mutex_.lock(); 19 if(pInstance_ == NULL) //線程的切換 20 pInstance_ = new Singleton; 21 mutex_.unlock(); 22 return pInstance_; 23 } 24 private: 25 Singleton() { } 26 27 static Singleton *pInstance_; 28 static MutexLock mutex_; 29 }; 30 31 Singleton *Singleton::pInstance_ = NULL; 32 MutexLock Singleton::mutex_; 33 34 class TestThread : public Thread 35 { 36 public: 37 void run() 38 { 39 const int kCount = 1000 * 1000; 40 for(int ix = 0; ix != kCount; ++ix) 41 { 42 Singleton::getInstance(); 43 } 44 } 45 }; 46 47 int main(int argc, char const *argv[]) 48 { 49 //Singleton s; ERROR 50 51 int64_t startTime = getUTime(); 52 53 const int KSize = 100; 54 TestThread threads[KSize]; 55 for(int ix = 0; ix != KSize; ++ix) 56 { 57 threads[ix].start(); 58 } 59 60 for(int ix = 0; ix != KSize; ++ix) 61 { 62 threads[ix].join(); 63 } 64 65 int64_t endTime = getUTime(); 66 67 int64_t diffTime = endTime - startTime; 68 cout << "cost : " << diffTime / 1000 << " ms" << endl; 69 70 return 0; 71 } 72 73 74 75 int64_t getUTime() 76 { 77 struct timeval tv; 78 ::memset(&tv, 0, sizeof tv); 79 if(gettimeofday(&tv, NULL) == -1) 80 { 81 perror("gettimeofday"); 82 exit(EXIT_FAILURE); 83 } 84 int64_t current = tv.tv_usec; 85 current += tv.tv_sec * 1000 * 1000; 86 return current; 87 }我們在線程訪問pInstance_之前加上了互斥變量, ?那么每次只能有一個線程訪問到該指針, 所以避免了new出多個Singleton對象導致內存泄露的問題。 ??
但是這樣也導致了一個問題, ?每個線程都要搶到鎖以后才能判斷 是否需要創建一個?Singleton對象, 其中搶到鎖以后,其他線程都堵塞在mutex_那行代碼, 浪費了大量的CPU系統資源。
?
3. 我們采用?DCLP(double-check-locking-pattern)來避免線程資源的浪費。
?
1 #include <iostream> 2 #include "MutexLock.h" 3 #include "Thread.h" 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include <sys/time.h> 8 using namespace std; 9 10 int64_t getUTime(); 11 12 //多線程下具有隱患 13 class Singleton 14 { 15 public: 16 static Singleton *getInstance() 17 { 18 if(pInstance_ == NULL) 19 { 20 mutex_.lock(); 21 if(pInstance_ == NULL) //線程的切換 22 pInstance_ = new Singleton; 23 mutex_.unlock(); 24 } 25 26 return pInstance_; 27 } 28 private: 29 Singleton() { } 30 31 static Singleton *pInstance_; 32 static MutexLock mutex_; 33 }; 34 35 Singleton *Singleton::pInstance_ = NULL; 36 MutexLock Singleton::mutex_; 37 38 class TestThread : public Thread 39 { 40 public: 41 void run() 42 { 43 const int kCount = 1000 * 1000; 44 for(int ix = 0; ix != kCount; ++ix) 45 { 46 Singleton::getInstance(); 47 } 48 } 49 }; 50 51 int main(int argc, char const *argv[]) 52 { 53 //Singleton s; ERROR 54 55 int64_t startTime = getUTime(); 56 57 const int KSize = 100; 58 TestThread threads[KSize]; 59 for(int ix = 0; ix != KSize; ++ix) 60 { 61 threads[ix].start(); 62 } 63 64 for(int ix = 0; ix != KSize; ++ix) 65 { 66 threads[ix].join(); 67 } 68 69 int64_t endTime = getUTime(); 70 71 int64_t diffTime = endTime - startTime; 72 cout << "cost : " << diffTime / 1000 << " ms" << endl; 73 74 return 0; 75 } 76 77 78 79 int64_t getUTime() 80 { 81 struct timeval tv; 82 ::memset(&tv, 0, sizeof tv); 83 if(gettimeofday(&tv, NULL) == -1) 84 { 85 perror("gettimeofday"); 86 exit(EXIT_FAILURE); 87 } 88 int64_t current = tv.tv_usec; 89 current += tv.tv_sec * 1000 * 1000; 90 return current; 91 }以上代碼和2的不同在于, 搶鎖之前先進行一次判斷pInstance_是否為空, 如果不為空那么不需要再搶鎖了直接return原指針, 避免堵塞。
那么為什么搶到鎖之后還有再判斷一次 該指針是否為空呢?? ?因為在最初的情況下(尚未new出一個對象時的零界條件下), 如果有2個線程同時通過了第一個判斷, 一個搶到鎖, 另一個被鎖堵塞,
如果鎖后面無判斷 指針是否為空, 依然導致 內存泄露, 因為被堵塞的線程在其后搶到鎖 依然會執行new, 導致內存泄露。
?
所以 采用?DCLP, 我們想象一下, 當Single同被創建出來, pInstance_ == NULL 始終為假, 使判斷后面的 搶鎖和再判斷 短路,這樣既可以保證安全, 又保證了效率。
?
以上?
?
轉載于:https://www.cnblogs.com/DLzhang/p/4014389.html
總結
以上是生活随笔為你收集整理的C++学习之路: 单例模板的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode——Same Tree(
- 下一篇: Android中asset文件夹和raw