標籤:-- 瓶頸 代碼 psi nbsp 問題 程式 線程 步驟
一、單例模式概念
在GOF的《設計模式:可複用物件導向軟體的基礎》中是這樣說的:保證一個類只有一個執行個體,並提供一個訪問它的全域訪問點。
1、為什麼要使用單例模式
- 1.在整個程式空間使用全域變數,共用資源;
- 2.大規模系統中,為了效能的考慮,需要節省對象的建立時間等等;
- 3.在多個線程之間,共用同一個資源或者操作同一個對象;
2、現單例常用步驟
- 1.建構函式private/protected
- 2.提供一個全域的靜態方法(全域訪問點)
- 3.在類中定義一個靜態指標,指向本類的變數的靜態變數指標
3、常見單例模式分類
執行個體初始化時間的不同,可以分為:餓漢式單例和懶漢式單例
- 1.餓漢式單例:靜態初始化的方式,它是類一載入就執行個體化的對象,需要提前佔用系統資源;
- 2.懶漢式單例:在第一次被引用時才會將自己執行個體化;存在多線程問題。
二、常見單例方式
1、懶漢模式 -- 經典
(1)不適合於在多線程的情況下建立單件對象
(2)不適合於需要在程式結束的時候調用具體類的解構函式
2、懶漢模式 – 經典 – 加鎖
線程不安全,怎麼辦?最直觀的的辦法:加鎖。但是這樣每次調用GetInstance()方法都需要Lock,會成為效能瓶頸。如何改進?
3、懶漢模式 – 雙檢鎖模式
1.為什麼要兩次判空?
a)第一次判空,如果m_pSingleton存在,就直接返回了;
b)pSingleton=NULL並且同時有兩個線程調用GetInstance()時,它們都可以通過第一次判空,然後由於Lock機制,只有一個線程進入,另一個在排隊等待;如果沒有第二次判空,那麼兩個都有可能建立執行個體
4、餓漢模式
類載入就執行個體化的對象,所以保證了安全執行緒,在效能要求比較高時,就可以使用這種方式,從而避免頻繁的加鎖和解鎖造成的資源浪費。
餓漢的實現是有隱患的,因為c++中對全域對象的構造順序並沒有明確的規定。假如有一個全域對象A 建構函式裡引用上文中餓漢形式的指標,若在A建構函式構造之前以上單例並未構造出來,那就會有問題。
5、餓漢模式 -- 靜態變數
因為靜態初始化在程式開始時,也就是進入main函數之前,由主線程以單線程方式完成了初始化,所以靜態初始化執行個體保證了執行緒安全性。在效能要求比較高時,就可以使用這種方式,從而避免頻繁的加鎖和解鎖造成的資源浪費。注意:禁止類拷貝和類賦值。
6、其他問題:記憶體是否需要釋放?
因為生命期與主線程相同的對象,程式退出,系統會回收所有資源,包括沒有delete的。或者在單例中實現一個private的Cgarbo:
7、單例模式與靜態方法的比較
1、單例可以繼承類,實現介面,而靜態類不能;
2、單例可以被延遲初始化,靜態類一般在第一次載入時初始化;
3、單例類可以被用於多態而無需強迫使用者只假定唯一的執行個體,不過一般不用這種方法,直接用模板實現單例比較省事!
4.單例模式執行的時候需要new 一個對象出來儲存在堆裡面,而靜態方法不需要,它不依賴於對象(沒有this指標),但是他也是需要記憶體的,儲存在靜態存區;
5.靜態方法的類會在代碼編譯的時候就被載入,靜態方法中產生的對象,會隨著靜態方法執行完畢而釋放掉,而且執行類中的靜態方法時,不會執行個體化靜態方法所在的類。如果用單例模式, 產生的那一個唯一的執行個體,會一直在記憶體中,不會被GC清除的(原因是靜態屬性變數不會被GC清除),除非程式退出了;
參考:大話設計模式
設計模式 -- 單例模式