目錄
什么是單例模式
什么是設計模式
單例模式的特點
餓漢實現方式和懶漢實現方式
餓漢方式實現單例模式
懶漢方式實現單例模式
懶漢方式實現單例模式(線程安全版本)
STL,智能指針和線程安全
STL中的容器是否是線程安全的?
智能指針是否是線程安全的?
其他常見的各種鎖
什么是單例模式
單例模式是一種 "經典的, 常用的, 常考的" 設計模式.
什么是設計模式
通俗的來講,IT行業這么火, 涌入的人很多. 俗話說林子大了啥鳥都有. 大佬和菜雞們兩極分化的越來越嚴重. 為了讓菜雞們不太拖大佬的后腿, 于是大佬們針對一些經典的常見的場景, 給定了一些對應的解決方案, 這個就是設計模式
單例模式的特點
某些類, 只應該具有一個對象(實例), 就稱之為單例.
例如一個男人只能有一個媳婦.
在很多服務器開發場景中, 經常需要讓服務器加載很多的數據 (上百G) 到內存中. 此時往往要用一個單例的類來管理這些數據.
餓漢實現方式和懶漢實現方式
[洗完的例子]
吃完飯, 立刻洗碗, 這種就是餓漢方式. 因為下一頓吃的時候可以立刻拿著碗就能吃飯.
吃完飯, 先把碗放下, 然后下一頓飯用到這個碗了再洗碗, 就是懶漢方式.
懶漢方式最核心的思想是 "延時加載". 從而能夠優化服務器的啟動速度.
餓漢方式實現單例模式
template <typename T> class Singleton { static T data; public: static T* GetInstance() { return &data; } };
只要通過 Singleton 這個包裝類來使用 T 對象, 則一個進程中只有一個T對象的實例.
懶漢方式實現單例模式
template <typename T> class Singleton { static T* inst; public: static T* GetInstance() { if (inst == NULL) { inst = new T(); } return inst; } };
存在一個嚴重的問題, 線程不安全.
第一次調用 GetInstance 的時候, 如果兩個線程同時調用, 可能會創建出兩份 T 對象的實例. 但是后續再次調用, 就沒有問題了.
所以,我們一般會在項目中帶上鎖。
懶漢方式實現單例模式(線程安全版本)
// 懶漢模式, 線程安全 template <typename T> class Singleton { volatile static T* inst; // 需要設置 volatile 關鍵字, 否則可能被編譯器優化. static std::mutex lock; public: static T* GetInstance() { if (inst == NULL) { // 雙重判定空指針, 降低鎖沖突的概率, 提高性能. lock.lock(); // 使用互斥鎖, 保證多線程情況下也只調用一次 new. if (inst == NULL) { inst = new T(); } lock.unlock(); } return inst; } };
注意事項:
1. 加鎖解鎖的位置
2. 雙重 if 判定, 避免不必要的鎖競爭
3. volatile關鍵字防止過度優化
STL,智能指針和線程安全
STL中的容器是否是線程安全的?
不是. 原因是, STL 的設計初衷是將性能挖掘到極致, 而一旦涉及到加鎖保證線程安全, 會對性能造成巨大的影響. 而且對于不同的容器, 加鎖方式的不同, 性能可能也不同(例如hash表的鎖表和鎖桶). 因此 STL 默認不是線程安全. 如果需要在多線程環境下使用, 往往需要調用者自行保證線程安全.
智能指針是否是線程安全的?
對于 unique_ptr, 由于只是在當前代碼塊范圍內生效, 因此不涉及線程安全問題.
對于 shared_ptr, 多個對象需要共用一個引用計數變量, 所以會存在線程安全問題.
但是標準庫實現的時候考慮到了這個問題, 基于原子操作(CAS)的方式保證 shared_ptr 能夠高效, 原子的操作引用計數.
其他常見的各種鎖
- 悲觀鎖:在每次取數據時,總是擔心數據會被其他線程修改,所以會在取數據前先加鎖(讀鎖,寫鎖,行鎖等),當其他線程想要訪問數據時,被阻塞掛起。
- 樂觀鎖:每次取數據時候,總是樂觀的認為數據不會被其他線程修改,因此不上鎖。但是在更新數據前, 會判斷其他數據在更新前有沒有對數據進行修改。主要采用兩種方式:版本號機制和CAS操作。
- CAS操作:當需要更新數據時,判斷當前內存值和之前取得的值是否相等。如果相等則用新值更新。若不等則失敗,失敗則重試,一般是一個自旋的過程,即不斷重試。
- 自旋鎖,公平鎖,非公平鎖?
- 1. 自旋鎖: 線程獲取鎖失敗時不阻塞,而是循環嘗試,適用于短時間持有鎖的多核場景。
- 2. 公平鎖: 鎖的獲取嚴格按照請求順序(FIFO),保證所有線程最終都能獲取鎖,避免饑餓。
- 3. 非公平鎖: 線程獲取鎖時直接競爭,允許插隊,犧牲公平性換取更高吞吐量。