單例模式Singleton背景
軟件實現中,有些時候整個系統只需要擁有一個全局對象,這樣有利於協調系統的整體行爲,使系統在單個對象存在時更有效率,或者限制實例化爲特定數量的對象。例如對某個協議棧模塊的訪問, 需要整個模塊有且僅有一個訪問入口,否則將會導致協議棧狀態混亂,爲此需要實現創建管理一個全局管理對象,維持其生命週期。
在軟件工程中,單例模式提供了這樣一個實現,保證了一個類只有一個實例存在,並提供一個訪問它的全局訪問點。
通過情況下,我們可以使用全局變量使得一個對象被全局訪問,但該方法不能防止實例化多個對象,無法保證實例唯一性,一個最好的辦法是,讓類自身負責保護它的唯一實例,這個類可以保證沒有其它實例被創建,並且提供一個訪問該實例的方法,實現類實例的唯一性和受控訪問。
單例模式Singleton實現
單例模式一個經典的實現方式:
class Singleton
{
private:
Singleton(){
//private constructor, 阻止外部調用new對象
}
Singleton(const Singleton&){
//private copy constructor, 防止被複制
};
Singleton& operator=(const Singleton&){
//private operator =, 防止被複制
};
private:
static Singleton *m_pInstance;
public:
static Singleton* GetInstance() //本類實例全局唯一訪問點
{
if(NULL == m_pInstance)
m_pInstance = new Singleton();
return m_pInstance;
}
static void DestoryInstance()
{
if (m_pInstance != NULL )
{
delete m_pInstance;
m_pInstance = NULL ;
}
}
// Other methods...
};
Singleton class通過將類的構造函數、複製構造函數以及賦值函數限定爲private,避免了類在外部被實例化,其唯一實例只能通過GetInstance()方法訪問。
多線程環境單例模式Singleton
上述單例模式實現代碼直觀易懂,但沒有考慮線程安全問題,在多線程的運行環境中,當多個線程同時訪問Singletion類的GetInstance()方法時,極有可能造成創建多個實例。若線程A運行檢測到m_pInstance == NULL, 但來得及執行m_pInstance = new Singleton()時被線程B中斷,線程B 執行同樣的判斷m_pInstance == NULL 從而創建了類的實例,當線程A再次獲得運行權時,將繼續執行創建類實例的操作,再次創建了實例,從而創建了多個實例,違背單例模式的初衷。因此比較直接的做法是,在創建實例時加鎖lock,lock 確保當線程A位於代碼lock保護區域時,其餘線程不能進入該lock保護區域,此時若其它線程試圖進入lock 保護區域,那麼它將一直被阻塞等待,直到鎖對象被釋放。
static Singleton* GetInstance() //本類實例全局唯一訪問點
{
lock(); //TODO: 借用其它類來實現, std::mutex
{
if(NULL == m_pInstance)
m_pInstance = new Singleton();
}
return m_pInstance;
}
這種實現方式的確保證了單例模式的線程安全,確保對象創建的唯一性,但每次調用GetInstance 獲取對象都需要lock 後判斷, 對性能有較大影響, 一種改進的做法是,先判斷對象是否已經創建,在未創建的情況下再加鎖創建, 確保創建的唯一性。
static Singleton GetInstance() //本類實例全局唯一訪問點
{
if(NULL == m_pInstance)
{
lock(); //TODO: 借用其它類來實現, std::mutex
{
if(null == m_pInstance)
m_pInstance = new Singleton();
}
}
return m_pInstance;
}
這種方式實現了在每次訪問GetInstance 時僅僅在對象不存在時需要加鎖lock,大大提高了效率。
說明: 單例模式有多種實現方式,文中只實現了其中的一種,逐步分析提供了在多線程環境中的安全機制。