單例模式
一個類只能被實例化一次,產生一個對象。
在類中,要構造一個實例,就需要通過構造函數,所以爲了防止在外部調用類的構造函數而構造實例,需要將構造函數的訪問權限標記爲protected或private;
並且需要提供給全局訪問點,就需要在類中定義一個static函數,返回在類內部唯一構造的實例。
懶漢式:在第一次用到類實例的時候纔會去實例化,訪問量較小時,採用懶漢式,以時間換空間。並不安全,因爲實例化並不是原子操作,可能在實例化的過程中,在給指針賦值之前,cpu切換線程導致產生多個對象。
餓漢式:在單例類定義的時候就進行實例化。訪問量較大、線程較多時,採用餓漢式,以空間換時間。線程安全,因爲一開始就實例化了。
懶漢式:
class Single
{
public:
static pthread_mutex_t mtx; 互斥鎖
***************在餓漢式中該函數只需要一個return***************
static Single* get_Single()
{
if (p == NULL) 雙重檢查
{
pthread_mutex_lock(&mtx);
if (p == NULL) 多線程,需要加鎖,保證其他線程無法獲取鎖而阻塞。
如果不加鎖,又因爲實例化不是原子操作,導致可能有多個進程創建多個對象
{
p = new Single; 如果還沒有唯一的對象就先生成,實例化,不是原子操作
}
pthread_mutex_unlock(&mtx);
}
return p;
}
private:
static Single* volatile p;
Single(){}
};
pthread_mutex_t Single::mtx = PTHREAD_MUTEX_INITIALIZER; 創建互斥鎖
***************懶漢與餓漢的區別就在於這個靜態成員變量實例化的位置不一樣***************
Single* Single::p = NULL; 靜態成員變量初始化 懶漢式在此處實例化
int main()
{
Single* tmp = Single::get_Single(); 用作用域加成員函數名調用靜態函數。
Single* tmp1 = Single::get_Single();
cout <<tmp <<endl<<tmp1<<endl;
return 0;
}
解決幾個問題:
1、爲什麼用靜態函數,靜態成員變量?
調用類裏的函數需要先構造對象,有對象才能調用類內成員方法。但這個方法肯定不適合單例模式。而另一種方法就是靜態函數,可以使用類名直接調用。而靜態函數只能調用靜態成員方法。
2、爲什麼加雙重檢查?
咱們從內向外分析,首先爲了滿足單例模式的要求,也就是說p創建了對象,之後就不能再創建了,外面就需要加一層判斷,保證在單線程的情況下可以順利實現單例。但這樣也僅僅在單線程下滿足,如果多線程,由於CPU時間片輪轉,很可能導致在賦給p途中被截斷,從而另一個進程也創建了對象,爲避免這種情況的發生,需要在外面加一互斥鎖。但加鎖的操作是由內核完成的,也就是說每次檢查是否持鎖都需要陷入內核。這種情況我們發現對於單線程又發生了不友好,單線程沒有必要取鎖解鎖消耗時間和資源。所以我們加了最外面的一層判斷,如果p已經不是NULL,我們直接跳過,不再進行鎖操作。
3、私有靜態成員變量volatile是個啥?
volatile:
1、防止多線程對共享變量進行線程緩存操作,一個線程修改了共享變量,另一個線程不能及時看到。
解釋一下:除了cpu寄存器上的空間,和內存,在介於兩者之間,還存在cache緩存區,多線程共享變量,在線程棧上緩存一份數據的拷貝,導致,cpu在取第二個指令的時候。誤認爲p指針還沒有實例化。
2、防止編譯器對涉及value操作的代碼進行指令重排序