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獲取對象對應類型的對象。這種方式可以通過統一的接口獲取操作,隱藏了具體實現,降低了耦合度。
參考
單例模式的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

