文章目錄
- 一、單例模式的特點
- 二、餓漢模式實現單例
- 三、懶漢模式實現單例
- 四、STL線程安全嗎?
- 五、智能指針線程安全嗎?
一、單例模式的特點
一個類,只應該實例化了一個對象,就是單例。
二、餓漢模式實現單例
舉個餓漢模式的例子:洗碗,一個人吃碗飯之后,他馬上洗碗,下次吃飯的時候就可以直接拿起碗吃飯了。這就是餓漢模式。
懶漢模式是:一個人吃完飯之后,他先不洗碗,下次吃飯的時候再洗碗,這就是懶漢模式。
懶漢模式的核心特點是:延遲加載。
餓漢模式實現單例:
template<class T>
class Singleton
{
private:static T inst;public:static T* GetInstance(){return &inst;}
};
通過SingleTon類實例化的對象只能擁有一個。
三、懶漢模式實現單例
懶漢模式具體是什么已經介紹過了。
下面用代碼實現懶漢模式
懶漢模式實現單例
template<class T>
class Singleton
{
private:static T* inst;public:static T* GetInstance(){if(inst == nullptr)inst = new T();return inst;}
};
但是這樣的懶漢模式存在線程安全,假如有兩個線程同時進入GetInstance函數中new T()呢?
這樣就會存在兩個inst指針指向的對象了, 就不再是單例了。
所以需要加鎖保證線程安全。
下面是線程安全版本的單例模式
template <class T>
class Singleton
{
private:static std::mutex lock;volatile static T* inst; //設置volatile關鍵字的目的是防止被編譯器優化
public:static T* GetInstance(){lock.lock();if(inst == nullptr) //判斷的本質也是再訪問臨界資源,所以要在加鎖之后inst = new T();lock.unlock();return inst;}
這樣解決了線程并發問題,但是這樣假如有大量線程同時進入該函數時,會并發競爭鎖,會造成性能低下,極端情況下可能出現卡頓現象。
因為不管有沒有實例化T對象,都會進行鎖競爭,這是不太合理的。
正確的應該是:如果沒有實例化T對象,先申請鎖對T對象new一個出來。
如果有了T對象,直接返回即可。
下面是改正后的代碼:
template <class T>
class Singleton
{
private:static std::mutex lock;volatile static T* inst; //設置volatile關鍵字的目的是防止被編譯器優化
public:static T* GetInstance(){if(inst == nullptr) //雙重判定空指針, 降低鎖沖突的概率, 提高性能{lock.lock();if(inst == nullptr) //判斷的本質也是再訪問臨界資源,所以要在加鎖之后inst = new T();lock.unlock();}return inst;}};
這樣實現懶漢模式的單例模式,解決了線程安全+性能低下的問題。
四、STL線程安全嗎?
不是
原因是, STL 的設計初衷是將性能挖掘到極致, 而一旦涉及到加鎖保證線程安全, 會對性能造成巨大的影響.而且對于不同的容器, 加鎖方式的不同, 性能可能也不同(例如hash表的鎖表和鎖桶).
因此 STL 默認不是線程安全. 如果需要在多線程環境下使用, 往往需要調用者自行保證線程安全
五、智能指針線程安全嗎?
對于 unique_ptr, 由于只是在當前代碼塊范圍內生效, 因此不涉及線程安全問題
對于 shared_ptr, 多個對象需要共用一個引用計數變量, 所以會存在線程安全問題.但是標準庫實現的時候考慮到了這個問題, 基于原子操作(CAS)的方式保證 shared_ptr 能夠高效, 原子的操作引用計數