Java單例模式分析
單例模式
為什么要用單例
確保某個類只有一個對象,常用于訪問數據庫操作,服務的配置文件等。
單例的關鍵點
1、默認構造函數為private,復制構造函數和復制賦值函數也要private或=delete禁用。(做到無法被外部其他對象構造)
2、通過一個靜態(tài)方法或枚舉返回單例類對象。
3、確保多線程的環(huán)境下,單例類對象只有一個。
幾種寫法
本文主要介紹C++的懶漢式和餓漢式寫法。
懶漢式
需要生成唯一對象時(調用GetInstance時),才生成
線程不安全的錯誤寫法
class SingleInstance { public: // 靜態(tài)方法獲取單例 static SingleInstance *GetInstance(); // 釋放單例避免內存泄露 static void deleteInstance(); private: SingleInstance() {} ~SingleInstance() {} // 復制構造函數和復制賦值函數設置為private,被禁用 SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal); private: static SingleInstance *m_SingleInstance; }; // 初始化為NULL,與后面形成對比 SingleInstance *SingleInstance::m_SingleInstance = NULL; SingleInstance* SingleInstance::GetInstance() { // 多線程情況下,一個線程通過if檢查但是還未new出單例時,另一個線程也通過了if檢查,導致new出多個對象 if (m_SingleInstance == NULL) { m_SingleInstance = new (std::nothrow) SingleInstance; } return m_SingleInstance; } void SingleInstance::deleteInstance() { if (m_SingleInstance) { delete m_SingleInstance; m_SingleInstance = NULL; } }
線程安全的雙檢鎖寫法
class SingleInstance { public: // 靜態(tài)方法獲取單例 static SingleInstance *GetInstance(); // 釋放單例避免內存泄露 static void deleteInstance(); private: SingleInstance() {} ~SingleInstance() {} // 復制構造函數和復制賦值函數設置為private,被禁用 SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal); private: static SingleInstance *m_SingleInstance; }; // 初始化為NULL,與后面形成對比 SingleInstance *SingleInstance::m_SingleInstance = NULL; SingleInstance* SingleInstance::GetInstance() { // 如果直接在外面鎖,功能也ok,但每次運行到這個地方便需要加一次鎖,非常浪費資源 // 在里面加鎖,初始化時存在加鎖的情況,初始化之后,外層if都為false,直接返回,避免加鎖 if (m_SingleInstance == NULL) { std::unique_lock<std::mutex> lock(m_Mutex); // Lock up if (m_SingleInstance == NULL) { m_SingleInstance = new (std::nothrow) SingleInstance; } } return m_SingleInstance; } void SingleInstance::deleteInstance() { if (m_SingleInstance) { delete m_SingleInstance; m_SingleInstance = NULL; } }
線程安全的局部靜態(tài)變量寫法
推薦
class SingleInstance { public: // 靜態(tài)方法獲取單例 static SingleInstance *GetInstance(); private: SingleInstance() {} ~SingleInstance() {} // 復制構造函數和復制賦值函數設置為private,被禁用 SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal); }; SingleInstance& SingleInstance::GetInstance() { // 局部靜態(tài)變量(一般為函數內的靜態(tài)變量)在第一次使用時分配內存并初始化。 static SingleInstance m_SingleInstance; return m_SingleInstance; }
餓漢式
進程運行前(main函數執(zhí)行),就創(chuàng)建
線程安全的進程運行前初始化寫法
class SingleInstance { public: // 靜態(tài)方法獲取單例 static SingleInstance *GetInstance(); private: SingleInstance() {} ~SingleInstance() {} // 復制構造函數和復制賦值函數設置為private,被禁用 SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal); private: static SingleInstance *m_SingleInstance; }; SingleInstance* SingleInstance::GetInstance() { return m_SingleInstance; } // 全局變量、文件域的靜態(tài)變量和類的靜態(tài)成員變量在main執(zhí)行之前的靜態(tài)初始化過程中分配內存并初始化 Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton; int main() { return 0; }
線程安全的類靜態(tài)成員變量寫法
class SingleInstance { public: // 靜態(tài)方法獲取單例 static SingleInstance *GetInstance(); // 全局變量、文件域的靜態(tài)變量和類的靜態(tài)成員變量在main執(zhí)行之前的靜態(tài)初始化過程中分配內存并初始化。 static SingleInstance m_SingleInstance; private: SingleInstance() {} ~SingleInstance() {} // 復制構造函數和復制賦值函數設置為private,被禁用 SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal); }; SingleInstance& SingleInstance::GetInstance() { return m_SingleInstance; }
靜態(tài)內部類寫法
JAVA
/** * 靜態(tài)內部類實現單例模式 */ public class Singleton { private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } /** * 靜態(tài)內部類 */ private static class SingletonHolder { private static Singleton instance = new Singleton(); } }
第一次加載Singleton類時不會初始化instance,只有在第一次調用getInstance()方法時,虛擬機會加載SingletonHolder類,初始化instance。
這種方式既保證線程安全,單例對象的唯一,也延遲了單例的初始化,推薦使用這種方式來實現單例模式。
枚舉單例
JAVA
/** * 枚舉實現單例模式 */ public enum SingletonEnum { INSTANCE; public void doSomething() { System.out.println("do something"); } }
默認枚舉實例的創(chuàng)建是線程安全的,即使反序列化也不會生成新的實例,任何情況下都是一個單例。
優(yōu)點: 簡單!
容器實現單例
JAVA
import java.util.HashMap; import java.util.Map; /** * 容器類實現單例模式 */ public class SingletonManager { private static Map<String, Object> objMap = new HashMap<String, Object>(); public static void regsiterService(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static Object getService(String key) { return objMap.get(key); } }
SingletonManager可以管理多個單例類型,使用時根據key獲取對象對應類型的對象。這種方式可以通過統(tǒng)一的接口獲取操作,隱藏了具體實現,降低了耦合度。
參考
單例模式的6種實現方式
軟件開發(fā)常用設計模式—單例模式總結(c++版)
https://stackoverflow.com/questions/1008019/c-singleton-design-pattern
https://programmer.ink/think/summary-of-c-thread-safety-singleton-patterns.html
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
Java異常處理UncaughtExceptionHandler使用實例代碼詳解
當一個線程由于未捕獲異常即將終止時,Java虛擬機將使用thread . getuncaughtexceptionhandler()查詢線程的uncaughtException處理程序,并調用處理程序的uncaughtException方法,將線程和異常作為參數傳遞2023-03-03