常見的設計模式--單例模式

設計模式

  設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類的、代碼設計經驗的總結。使用設計模式的目的是爲了代碼可重用性、讓代碼更容易被他人理去解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

單例模式

  一個類只能創建一個對象,即單例模式,該模式可以保證系統中該類只有一個實例,並提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在複雜環境下的配置管理。

餓漢模式

  餓漢模式就是在程序運行之前將所有的資源一次性創建好,在之後用的時候就會很方便。由於靜態的變量是在進入主函數之前就創建好了,所以要定義一個靜態變量,然後將構造函數和拷貝構造設置爲私有的,拷貝構造函數要只聲明不定義,並在類中給出一個創建對象的靜態方法,這個靜態方法只能是引用返回,如果值返回,就會調用拷貝構造,但是拷貝構造調不了,即使可以調就會創建一個對象,這樣就和單例模式相沖突。雖然這種方式在程序運行之後不用再去創建資源,直接調用就可以,但是如果這份資源很大,那麼程序就會啓動的很慢。

class Singleton 
{
public:
	static Singleton* GetInstance()
	{
		return &p;
	}

private:
	//構造函數私有
	Singleton()
	{}
	//防止被拷貝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	static Singleton p;
};

Singleton Singleton::p;

  使用場景

  在多線程高併發環境下頻繁使用,性能要求較高,那麼顯然使用餓漢模式來避免資源競
爭,提高響應速度更好。

懶漢模式

  實現過程:
  懶漢模式就是在需要的時候纔會去創建 構造函數和拷貝構造函數都要設爲私有的,同樣的在類中給出一個創建靜態的方法,並且在類中創建一個靜態的指針,在調用這個方法時如果這個靜態的指針是空的,就爲這個指針new一個對象,返回去。如果是在多線程的環境中,就會出現多個線程同時去申請資源,這時候就要多線程中常用的加解鎖操作,如果每次都要先加鎖在檢測,這時候所有的線程都會阻塞在加鎖這裏,等解鎖後纔可以繼續操作,所以使用DCL雙檢鎖,這時如果有一個線程在進行加解鎖操作,另一個線程也過來了,這時候資源已經申請好了,這個後來的線程就可以直接返回,不用阻塞等待之前的線程。但是這樣的版本仍然是有問題的,如果線程A正在進行對象的的創建,線程B過來了,但是A現在只是申請了空間,還沒有進行實例化,但是B檢測到這個對象不爲空,直接返回去使用,就會出問題,所以要將靜態對象加一個volatile來限定一下,告訴系統每次取變量裏面的信息的時候不要從寄存器中取,要到內存裏面取,這樣就禁止了編譯器對創建對象的次序進行優化(申請空間->構造對象->賦值—>申請空間->賦值->構造對象)。雖然改進了這麼多,但是這個代碼還存在問題,那就是沒有釋放空間,可能會存在內存泄漏。如果要釋放,就要保證所有的線程已經用完了這份資源,但是不能在類中直接給出一個靜態的釋放函數,這樣有可能會忘記調用這個函數,最好的方法就是內嵌一個內部類來實現釋放。

#include <mutex>
#include <thread>

using namespace std;

class Singleton1
{
public:
	volatile Singleton1* GetInstance()
	{
		if (p == nullptr)//要採用DCL雙檢鎖,讓其他的線程可以不用等,直接返回。
			//這樣如果編譯器對代碼進行了優化,將創建對象的順序重新調整,直接返回就會出錯。
		{
			m_tex.lock();//如果這裏只加這一個鎖,然後去判斷,其他線程會阻塞在這裏等待解鎖。
			if (p == nullptr)
				 p = new Singleton1;
			m_tex.unlock();
		}

		return p;
	}

	//在釋放資源的時候要保證所有線程已經將這份資源用完,但是不能直接在類中給出一個釋放資源的函數,有可能忘記調用這個函數
	//最好的方法是在類中內嵌一個類負責資源釋放
	class Clean
	{
	public:
		~Clean()
		{
			if (Singleton1::p)
			{
				delete Singleton1::p;
				Singleton1::p = nullptr;
			}
		}

	};

	static Clean c;

private:
	Singleton1()
	{}
	Singleton1(const Singleton1&) = delete;
	Singleton1& operator=(const Singleton1&) = delete;
private:
	static  Singleton1 volatile *p;
	static mutex m_tex;
};
//爲對象添加volatile關鍵字,告訴系統取變量裏面的信息的時候從內從中取,這樣就禁止了編譯器對變量的創建順序進行優化
//但是這樣還不夠,就是沒有釋放空間,會造成內存泄漏。
volatile Singleton1* Singleton1::p = nullptr;
mutex Singleton1::m_tex;
Singleton1::Clean c;

  使用場景

  如果單例對象構造十分耗時或者佔用很多資源,比如加載插件啊, 初始化網絡連接啊,讀取文件啊等等,而有可能該對象程序運行時不會用到,那麼也要在程序一開始就進行初始化,就會導致程序啓動時非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。

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