設計模式——單例模式

設計模式:設計模式

設計模式表明了最佳實踐,是軟件開發過程當中面臨通常問題的解決方案。 設計模式是一套被反覆使用、通過分類、代碼設計總結的經驗。安全

單例模式

單例模式也叫單件模式。Singleton是一個很是經常使用的設計模式,幾乎全部稍微大一些的程序都會使用到它,因此構建一個線程安全而且 高效的Singleton很重要。多線程

1. 單例類保證全局只有一個惟一實例對象。 併發

2. 單例類提供獲取這個惟一實例的接口。函數

因爲要求只生成一個實例,所以咱們必須把構造函數的訪問權限標記爲protected或private,限制只能在類內建立對象.高併發

單例類要提供一個訪問惟一實例的接口函數(全局訪問點),就須要在類中定義一個static函數,返回在類內部惟一構造的實例。性能

(這樣還能夠確保直接用類名就能訪問到該惟一實例,沒必要用到實例化出的對象名去調用)優化


 兩個概念:spa

 懶漢模式 (lazy loading ):第一次調用GetInstance才建立實例對象,比較複雜
 餓漢模式:  程序一運行,就建立實例對象、簡潔高效 ,但有些場景下不適用 線程

方法一:不考慮線程安全,只適用於單線程環境的單例類

定義一個靜態的實例,在須要的時候建立該實例 (懶漢模式)

class Singleton
{
public:
	//獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		if (_instance == NULL)
		{
			_instance = new Singleton();
		}
		return _instance;
	}
	static void DelInstance()
	{
		if (_instance != NULL)
		{
			delete _instance;
			_instance = NULL;
		}
	}
	void Print()
	{
		cout << _data << endl;
	}
protected:
	//構造函數標記爲protected或private,限制只能在類內建立對象
	Singleton()
		:_data(5)
	{}

	//防拷貝
	Singleton(const Singleton&);
	Singleton operator=(const Singleton&);
private:		
	//指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例
	static Singleton* _instance;	  // 單實例對象
	int _data;  //單實例對象中的數據
};
// 靜態成員在類外初始化
Singleton* Singleton::_instance = NULL;

  這種方法是最簡單、最廣泛的方法。只有在_instance爲NULL的時候纔會建立一個實例以免重複建立。同時咱們把構造函數定義爲私有函數,這樣就能確保只建立一個實例。

可是上述的代碼在單線程的時候工做正常,在多線程的狀況下就有問題了。

  設想若是兩個線程同時運行到判斷_instance是否爲NULL的 if 語句那裏,而且_instance以前並未建立時,這兩個線程各自就都會建立一實例,這是就沒法知足單例模式的要求了。


 方法二:能在多線程環境下工做,可是效率不高

爲了保障在多線程環境下只獲得一個實例,須要加一把互斥鎖。把上述代碼稍做修改,即:

ps: 下面部分的加鎖使用了C++11庫的互斥鎖

class Singleton
{
public:
	//獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		//lock();        //C++中沒有直接的lock()
		//RAII
		//lock lk;
		_sMtx.lock();   //C++11
		if (_instance == NULL)
		{
			_instance = new Singleton();
		}
		//unlock();
		_sMtx.unlock();
		return _instance;
	}
	static void DelInstance()
	{
		if (_instance != NULL)
		{
			delete _instance;
			_instance = NULL;
		}
	}
	void Print()
	{
		cout << _data << endl;
	}
protected:
	//構造函數標記爲protected或private,限制只能在類內建立對象
	Singleton()
		:_data(5)
	{}

	//防拷貝
	Singleton(const Singleton&);
	Singleton operator=(const Singleton&);

private:
	//指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例
	static Singleton* _instance;	  // 單實例對象
	int _data;								  // 單實例對象中的數據
	static mutex _sMtx;	              // 互斥鎖
};
// 靜態成員在類外初始化
Singleton* Singleton::_instance = NULL;
mutex Singleton::_sMtx;

  設想有兩個線程同時想建立一個實例,因爲在一個時刻,只有一個線程能獲得互斥鎖,因此當第一個線程加上鎖後,第二個線程就只能等待。當第一個線程發現實例尚未建立時,它就創建一個實例。接着第一個線程釋放鎖,此時第二個線程進入並上鎖,這個時候因爲實例已經被第一個線程建立出來了,第二個線程就不會重複建立實例了,這樣就保證在多線程環境下只能獲得一個實例。

  可是,每次獲取惟一實例,程序都會加鎖,而加鎖是一個很是耗時的操做,在沒有必要的時候,咱們要儘可能避免,不然會影響性能。


 方法三:使用雙重檢查,提升效率,避免高併發場景下每次獲取實例對象都進行加鎖,並使用內存柵欄防止重排

class Singleton
{
public:
	//獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		// 使用雙重檢查,提升效率,避免高併發場景下每次獲取實例對象都進行加鎖
		if (_instance == NULL)
		{
			std::lock_guard<std::mutex> lck(_sMtx);
			if (_instance == NULL)
			{
				// tmp = new Singleton()分爲如下三個部分
				// 1.分配空間2.調用構造函數3.賦值
				// 編譯器編譯優化可能會把2和3進行指令重排,這樣可能會致使高併發場景下,其餘線程獲取到未調用構造函數初始化的對象
				// 如下加入內存柵欄進行處理,防止編譯器重排柵欄後面的賦值到內存柵欄以前
				Singleton* tmp = new Singleton();
				MemoryBarrier(); //內存柵欄
				_instance = tmp;
			}
		}
		return _instance;
	}
	static void DelInstance()
	{
		if (_instance != NULL)
		{
			delete _instance;
			_instance = NULL;
		}
	}
	void Print()
	{
		cout << _data << endl;
	}
protected:
	//構造函數標記爲protected或private,限制只能在類內建立對象
	Singleton()
		:_data(5)
	{}

	//防拷貝
	Singleton(const Singleton&);
	Singleton operator=(const Singleton&);

private:
	//指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例
	static Singleton* _instance;	  // 單實例對象
	int _data;								  // 單實例對象中的數據
	static mutex _sMtx;	              // 互斥鎖
};
// 靜態成員在類外初始化
Singleton* Singleton::_instance = NULL;
mutex Singleton::_sMtx;

  試想,當實例還未建立時,因爲 Singleton == NULL ,因此很明顯,兩個線程均可以經過第一重的 if 判斷 ,進入第一重 if 語句後,因爲存在鎖機制,因此會有一個線程進入 lock 語句並進入第二重 if 判斷 ,而另外的一個線程則會在 lock 語句的外面等待。而當第一個線程執行完 new  Singleton()語句退出鎖定區域,第二個線程即可以進入 lock 語句塊,此時,若是沒有第二重Singleton == NULL的話,那麼第二個線程仍是能夠調用 new  Singleton()語句,第二個線程仍舊會建立一個 Singleton 實例,這樣也仍是違背了單例模式的初衷的,因此這裏必需要使用雙重檢查鎖定(第二層if 判斷必須存在)。

   多數現代計算機爲了提升性能而採起亂序執行,這使得內存柵欄成爲必須。barrier就象是代碼中的一個柵欄,將代碼邏輯分紅兩段,barrier以前的代碼和barrier以後的代碼在通過編譯器編譯後順序不能亂掉。也就是說,barrier以後的代碼對應的彙編,不能跑到barrier以前去,反之亦然。之因此這麼作是由於在咱們這個場景中,若是編譯器爲了榨取CPU的performace而對彙編指令進行重排,其它線程獲取到未調用構造函數初始化的對象,頗有可能致使出錯。

   只有第一次調用_instance爲NULL,而且試圖建立實例的時候才須要加鎖,當_instance已經建立出來後,則不必加鎖。這樣的修改比以前的時間效率要好不少。

可是這樣的實現比較複雜,容易出錯,咱們還能夠利用餓漢模式,建立相對簡潔高效的單例模式。


方法四:餓漢模式--簡潔、高效、不用加鎖、可是在某些場景下會有缺陷

  由於靜態成員的初始化在程序開始時,也就是進入主函數以前,由主線程以單線程方式完成了初始化,因此靜態初始化實例保證了線程安全性。在性能要求比較高時,就可使用這種方式,從而避免頻繁的加鎖和解鎖形成的資源浪費。

class Singleton
{
public:
	//獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		assert(_instance);
		return _instance;
	}
	void Print()
	{
		cout << _data << endl;
	}
protected:
	//構造函數標記爲protected或private,限制只能在類內建立對象
	Singleton()
		:_data(5)
	{}

	//防拷貝
	Singleton(const Singleton&);
	Singleton operator=(const Singleton&);

private:
	static Singleton* _instance;	  // 單實例對象
	int _data;			 // 單實例對象中的數據
};
Singleton* Singleton::_instance = new Singleton;

 代碼實現很是簡潔。建立的實例_instance並非在第一次調用GetInstance接口函數時才建立,而是在初始化靜態變量的時候就建立一個實例。若是按照該方法會過早的建立實例,從而下降內存的使用效率。 

方法五:方法四還能夠再簡化點

class Singleton
{
public:
	//獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		static Singleton instance;
		return &instance;
	}
	void Print()
	{
		cout << _data << endl;
	}
protected:
	//構造函數標記爲protected或private,限制只能在類內建立對象
	Singleton()
		:_data(5)
	{}

	//防拷貝
	Singleton(const Singleton&);
	Singleton operator=(const Singleton&);

private:
	int _data;	 // 單實例對象中的數據
};

 實例銷燬

 此處使用了一個內部GC類,而該類的做用就是用來釋放資源

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//帶RAII GC自動回收實例對象的方式
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Singleton
{
public:
	// 獲取惟一對象實例的接口函數
	static Singleton* GetInstance()
	{
		assert(_instance);
		return _instance;
	}
	// 刪除實例對象
	static void DelInstance()
	{
		if (_instance)
		{
			delete _instance;
			_instance = NULL;
		}
	}
	void Print()
	{
		cout << _data << endl;
	}
	class GC
	{
	public:
		~GC()
		{
			cout << "DelInstance()" << endl;
			DelInstance();
		}
	};
private:
	Singleton()
		:_data(5)
	{}
	static Singleton*_instance;
	int _data;
};
// 靜態對象在main函數以前初始化,這時只有主線程運行,因此是線程安全的。
Singleton* Singleton::_instance = new Singleton;
// 使用RAII,定義全局的GC對象釋放對象實例
Singleton::GC gc;

    在程序運行結束時,系統會調用Singleton中GC的析構函數,該析構函數會進行資源的釋放。

相關文章
相關標籤/搜索