Java單例模式分析
單例模式
為什么要用單例
確保某個(gè)類只有一個(gè)對象,常用于訪問數(shù)據(jù)庫操作,服務(wù)的配置文件等。
單例的關(guān)鍵點(diǎn)
1、默認(rèn)構(gòu)造函數(shù)為private,復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)也要private或=delete禁用。(做到無法被外部其他對象構(gòu)造)
2、通過一個(gè)靜態(tài)方法或枚舉返回單例類對象。
3、確保多線程的環(huán)境下,單例類對象只有一個(gè)。
幾種寫法
本文主要介紹C++的懶漢式和餓漢式寫法。
懶漢式
需要生成唯一對象時(shí)(調(diào)用GetInstance時(shí)),才生成
線程不安全的錯(cuò)誤寫法
class SingleInstance
{
public:
// 靜態(tài)方法獲取單例
static SingleInstance *GetInstance();
// 釋放單例避免內(nèi)存泄露
static void deleteInstance();
private:
SingleInstance() {}
~SingleInstance() {}
// 復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)設(shè)置為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()
{
// 多線程情況下,一個(gè)線程通過if檢查但是還未new出單例時(shí),另一個(gè)線程也通過了if檢查,導(dǎo)致new出多個(gè)對象
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();
// 釋放單例避免內(nèi)存泄露
static void deleteInstance();
private:
SingleInstance() {}
~SingleInstance() {}
// 復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)設(shè)置為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,但每次運(yùn)行到這個(gè)地方便需要加一次鎖,非常浪費(fèi)資源
// 在里面加鎖,初始化時(shí)存在加鎖的情況,初始化之后,外層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() {}
// 復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)設(shè)置為private,被禁用
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
};
SingleInstance& SingleInstance::GetInstance()
{
// 局部靜態(tài)變量(一般為函數(shù)內(nèi)的靜態(tài)變量)在第一次使用時(shí)分配內(nèi)存并初始化。
static SingleInstance m_SingleInstance;
return m_SingleInstance;
}
餓漢式
進(jìn)程運(yùn)行前(main函數(shù)執(zhí)行),就創(chuàng)建
線程安全的進(jìn)程運(yùn)行前初始化寫法
class SingleInstance
{
public:
// 靜態(tài)方法獲取單例
static SingleInstance *GetInstance();
private:
SingleInstance() {}
~SingleInstance() {}
// 復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)設(shè)置為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)初始化過程中分配內(nè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)初始化過程中分配內(nèi)存并初始化。
static SingleInstance m_SingleInstance;
private:
SingleInstance() {}
~SingleInstance() {}
// 復(fù)制構(gòu)造函數(shù)和復(fù)制賦值函數(shù)設(shè)置為private,被禁用
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
};
SingleInstance& SingleInstance::GetInstance()
{
return m_SingleInstance;
}
靜態(tài)內(nèi)部類寫法
JAVA
/**
* 靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式
*/
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 靜態(tài)內(nèi)部類
*/
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
第一次加載Singleton類時(shí)不會(huì)初始化instance,只有在第一次調(diào)用getInstance()方法時(shí),虛擬機(jī)會(huì)加載SingletonHolder類,初始化instance。
這種方式既保證線程安全,單例對象的唯一,也延遲了單例的初始化,推薦使用這種方式來實(shí)現(xiàn)單例模式。
枚舉單例
JAVA
/**
* 枚舉實(shí)現(xiàn)單例模式
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do something");
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,即使反序列化也不會(huì)生成新的實(shí)例,任何情況下都是一個(gè)單例。
優(yōu)點(diǎn): 簡單!
容器實(shí)現(xiàn)單例
JAVA
import java.util.HashMap;
import java.util.Map;
/**
* 容器類實(shí)現(xiàn)單例模式
*/
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可以管理多個(gè)單例類型,使用時(shí)根據(jù)key獲取對象對應(yīng)類型的對象。這種方式可以通過統(tǒng)一的接口獲取操作,隱藏了具體實(shí)現(xiàn),降低了耦合度。
參考
單例模式的6種實(shí)現(xiàn)方式
軟件開發(fā)常用設(shè)計(jì)模式—單例模式總結(jié)(c++版)
https://stackoverflow.com/questions/1008019/c-singleton-design-pattern
https://programmer.ink/think/summary-of-c-thread-safety-singleton-patterns.html
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
三分鐘帶你掌握J(rèn)ava開發(fā)圖片驗(yàn)證碼功能方法
這篇文章主要來為大家詳細(xì)介紹Java實(shí)現(xiàn)開發(fā)圖片驗(yàn)證碼的具體方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2023-02-02
解決mybatis case when 報(bào)錯(cuò)的問題
這篇文章主要介紹了解決mybatis case when 報(bào)錯(cuò)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
基于web項(xiàng)目log日志指定輸出文件位置配置方法
下面小編就為大家分享一篇基于web項(xiàng)目log日志指定輸出文件位置配置方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04
springboot項(xiàng)目連接多種數(shù)據(jù)庫該如何操作詳析
在Spring Boot應(yīng)用中連接多個(gè)數(shù)據(jù)庫或數(shù)據(jù)源可以使用多種方式,下面這篇文章主要給大家介紹了關(guān)于springboot項(xiàng)目連接多種數(shù)據(jù)庫該如何操作的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
如何查看Linux上正在運(yùn)行的所有Java程序列表
在linux操作時(shí),經(jīng)常要查看運(yùn)行的項(xiàng)目的進(jìn)程和端口,下面這篇文章主要給大家介紹了關(guān)于如何查看Linux上正在運(yùn)行的所有Java程序列表的相關(guān)資料,需要的朋友可以參考下2023-10-10
Java異常處理UncaughtExceptionHandler使用實(shí)例代碼詳解
當(dāng)一個(gè)線程由于未捕獲異常即將終止時(shí),Java虛擬機(jī)將使用thread . getuncaughtexceptionhandler()查詢線程的uncaughtException處理程序,并調(diào)用處理程序的uncaughtException方法,將線程和異常作為參數(shù)傳遞2023-03-03

