C++:05.單例模式

單例模式

一個類只能被實例化一次,產生一個對象。

在類中,要構造一個實例,就需要通過構造函數,所以爲了防止在外部調用類的構造函數而構造實例,需要將構造函數的訪問權限標記爲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操作的代碼進行指令重排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章