餓漢單例模式
通過單例模式可以保證系統中,應用該模式的類乙個類只有乙個例項(即乙個類只有乙個物件例項),並提供乙個訪問它的全域性訪問點,該例項被所有程式模組共享。
《設計模式》一書中給出了一種很不錯的實現,定義乙個單例類,使用類的私有靜態指標變數指向類的唯一例項,並用乙個公有的靜態方法獲取該例項。
分點表述:
給單例模式的類提供私有的建構函式
在該類中定義乙個靜態私有物件的指標
提供乙個靜態的公有方法用於建立或獲取它本身的靜態私有物件
懶漢單例模式:第一次使用時才建立乙個唯一的例項物件,從而實現延遲載入的效果。
餓漢單例模式:只要程式啟動就會建立乙個唯一的例項物件,即單例類定義的時候就進行例項化。
將建構函式私有化
類定義時不建立例項,首次使用的時候再進行建立
建立靜態成員變數(minstance)
建立公有的訪問介面getsingleton()
定義內嵌類進行物件的銷毀
class
csingleton
return minstance;
}class
cdestroy}}
;static cdestroy mdel;
//當該靜態變數銷毀時會呼叫 ~cdestroy(),進而銷毀minstance
private
:static csingleton * minstance;
csingleton()
};csingleton* csingleton :
: minstance = null;
csingleton:
:cdestroy mdel;
intmain()
懶漢單例模式存在的執行緒安全問題
懶漢單例模式的安全版本
加鎖由於懶漢單例模式的上述情況在多執行緒的情況下會可能導致不安全狀況的發生,因此我們必須加一控制。在這裡我們採用加鎖機制
靜態初始化互斥鎖:用乙個巨集pthread_mutex_initializer來靜態初始化互斥鎖
class
csingleton
pthread_mutex_unlock
(&mutex)
;//構造完物件後進行解鎖操作
cout <<
"unlock()"
<< endl;
return msin;
}class
cdestroy}}
;static cdestroy mdel;
private
:csingleton()
static csingleton* minstance;
static pthread_mutex_t mutex;};
csingleton* csingleton:
:minstance = null;
pthread_mutex_t csingleton:
:mutex = pthread_mutex_initializer;
//定義乙個鎖並進行初始化
csingleton:
:cdestroy mdel;
intmain()
加鎖的優化
雖然上述的加鎖機制已經解決了我們的多執行緒安全問題,但是每次進行判斷之前都要進行加鎖以及之後的解鎖操作,會大大降低我們的效率。
考慮到只有第一次建立物件的時候需要進行加鎖,只要第一次建立物件成功以後,就不必再進行加鎖和解鎖的操作,只需返回乙個指向原來已經建立好的物件的指標即可。
修改函式getsingleton()
static csingleton*
getsingleton()
pthread_mutex_unlock
(&mutex)
;//解鎖
cout <<
"unlock()"
<< endl;
}return minstance;
}
雙重if語句的原因
在多執行緒的環境中有這樣一種情況就是在第一次建立物件的時候兩個執行緒同時到達,即同時呼叫 getsingleton()方法
而此時由於 minstance 為 null,則兩個執行緒都會進入第一重if語句裡邊。
由於存在鎖機制,所以會有乙個執行緒進入 lock 語句並進入第二個minstance== null ,而另外的乙個執行緒則會在 lock 語句的外面等待。
而當第乙個執行緒執行完 new csingleton()語句後,便會解鎖。
此時,第二個執行緒便可以進入 lock 語句塊,此時,如果沒有第二重 minstance== null 的話,那麼第二個執行緒還是可以呼叫 new csingleton()語句,這樣第二個執行緒也會建立乙個 singleton例項,這樣也還是違背了單例模式的初衷的。
使用區域性靜態變數實現懶漢單例模式的安全版本
利用靜態區域性變數的其中一條性質函式內部的靜態區域性變數的初始化是在函式第一次呼叫時執行; 在之後的呼叫中不會對其初始化。 在多執行緒環境下,仍能夠保證靜態區域性變數被安全地初始化,並只初始化一次。
#include
using namespace std;
class
csingleton
~csingleton()
private
:csingleton()
};intmain()
雖然minstance 是靜態的區域性變數,但是編譯器在處理上與全域性靜態變數類似, 均儲存在 bss 段
函式中必須要使用static變數的情況:當某函式的返回值為指標型別時,則必須是static的區域性變數的位址作為返回值,若為auto型別,則返回為錯指標。
那麼問題來了??為什麼要使用靜態變數,和靜態成員方法??建構函式私有化
在全域性進行建立物件
讓私有的靜態類指標指向類的物件
公有的靜態成員方法作為對外的介面
定義乙個靜態的巢狀類物件的析構函式間接析構單例物件
class
csingleton
private
:static csingleton *minstance;
csingleton()
};csingleton* csingleton :
: minstance =
newcsingleton()
;int
main()
把delete封裝起來遇到的問題class
csingleton
~csingleton()
//使用new申請的記憶體系統是不會主動**的,需要手動刪除,因此我們需要自定義的析構函式delete
解決辦法
class
csingleton
~csingleton()
class
cdestroy}}
;static cdestroy mdel;
private
:static csingleton * minstance;
csingleton()
};csingleton* csingleton :
: minstance =
newcsingleton()
;csingleton:
:cdestroy mdel;
intmain()
我們知道,程式在結束的時候,系統會自動析構所有的全域性變數。事實上,系統也會析構所有的類的靜態成員變數,就像這些靜態成員也是全域性變數一樣。利用這個特點,我們可以在單例類中定義乙個這樣的靜態成員變數(mdel),而它的唯一工作就是在析構函式中刪除單例類的例項。
類cdestroy被定義為csingleton的公有內嵌類。程式執行結束時,系統會呼叫csingleton的靜態成員mdel的析構函式,該析構函式會刪除單例的唯一例項。
餓漢模式問題
餓漢模式雖然是執行緒安全的,但是在實際開發中,會有一些大家需要注意的問題。
基於以上兩點,大家在使用餓漢模式的時候需要注意實際的使用場景。
單例模式 餓漢模式 懶漢模式
構造方法私有化 靜態屬性指向例項 public static的 getinstance方法,返回第二步的靜態屬性 餓漢式是立即載入的方式,無論是否會用到這個物件,都會載入。如果在構造方法裡寫了效能消耗較大,佔時較久的 比如建立與資料庫的連線,那麼就會在啟動的時候感覺稍微有些卡頓。懶漢式,是延遲載入的...
懶漢餓漢單例模式
懶漢式單例類,在第一次呼叫時的時候例項化自己 public class singleton private static singleton single null 靜態工廠方法 public static singleton getinstance return single 執行緒安全的懶漢式單...
單例模式 懶漢,餓漢
1.懶漢模式 顧名思義,他是乙個懶漢,他不願意動彈。什麼時候需要吃飯了,他就什麼時候開始想辦法搞點食物。即懶漢式一開始不會例項化,什麼時候用就什麼時候new,才進行例項化。2.餓漢模式 顧名思義,他是乙個餓漢,他很勤快就怕自己餓著。他總是先把食物準備好,什麼時候需要吃了,他隨時拿來吃,不需要臨時去搞...