單例模式的寫法(保證線程安全)

1. 引言

1.1 什么是單例模式?

單例模式(Singleton Pattern)是一種創建型設計模式,它確保一個類只有一個實例,并提供一個全局訪問點
核心思想:控制實例化過程,避免重復創建對象。

1.2 為什么需要單例模式?

  • 節省資源:某些對象(如數據庫連接池、日志管理器)只需要一個實例。

  • 全局訪問:方便在多個模塊中共享數據。

  • 避免沖突:如配置文件管理,防止多個實例修改導致不一致。

1.3 線程安全的重要性

  • 多線程環境下,如果不加控制,可能導致:

    • 創建多個實例(違反單例原則)。

    • 對象狀態不一致(如緩存數據被覆蓋)。

  • 目標:確保在任何情況下,單例類只被初始化一次


2. 單例模式的實現方式

2.1 餓漢式(Eager Initialization)

代碼實現
public class Singleton {// 類加載時就初始化(線程安全由JVM保證)private static final Singleton INSTANCE = new Singleton();// 私有構造方法,防止外部newprivate Singleton() {}// 全局訪問點public static Singleton getInstance() {return INSTANCE;}
}
特點
  • 優點

    • 實現簡單,線程安全(由類加載機制保證)。

    • 沒有鎖,性能高。

  • 缺點

    • 即使不用也會創建實例,可能浪費內存。

    • 無法傳遞參數初始化(如需要動態配置)。

適用場景
  • 實例占用內存小,且一定會被使用(如簡單配置類)。


2.2 懶漢式(Lazy Initialization)

基礎版(非線程安全)
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton(); // 多線程下可能創建多個實例}return instance;}
}

問題:多線程環境下,多個線程可能同時進入?if (instance == null),導致多次實例化。


加鎖版(線程安全但低效)
public class Singleton {private static Singleton instance;private Singleton() {}// 方法加鎖,保證線程安全public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

問題:每次調用?getInstance()?都會加鎖,性能差(99%的情況不需要同步)。


2.3 雙重檢查鎖(Double-Checked Locking, DCL)

代碼實現
public class Singleton {// volatile 禁止指令重排序,避免半初始化問題private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次檢查(無鎖,提高性能)synchronized (Singleton.class) {if (instance == null) { // 第二次檢查(防止多線程競爭)instance = new Singleton();}}}return instance;}
}
關鍵點
  1. volatile?的作用

    • 防止指令重排序(避免返回未初始化的對象)。

    • 保證可見性(一個線程修改后,其他線程立即可見)。

  2. 兩次判空

    • 第一次檢查(無鎖):提高性能,避免每次加鎖。

    • 第二次檢查(加鎖):防止多線程競爭導致多次實例化。

優點
  • 線程安全?+?高性能(只有第一次初始化需要同步)。

缺點
  • 代碼稍復雜,需理解?volatile?和指令重排序。


2.4 靜態內部類(Holder Class)

代碼實現
public class Singleton {private Singleton() {}// 靜態內部類(延遲加載)private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE; // 調用時才加載}
}
原理
  • 類加載機制SingletonHolder?只有在?getInstance()?被調用時才會加載。

  • 線程安全:由 JVM 保證靜態內部類的初始化是線程安全的。

優點
  • 線程安全 + 延遲加載 + 無鎖高性能。

  • 比雙重檢查鎖更簡潔。

缺點
  • 無法傳遞參數初始化(適合無參構造)。


2.5 枚舉(Enum Singleton)

代碼實現
public enum Singleton {INSTANCE; // 單例實例public void doSomething() {System.out.println("Singleton method");}
}
原理
  • JVM 保證枚舉唯一性:枚舉實例在類加載時初始化,且不可反射創建。

優點
  1. 絕對線程安全(JVM 保證)。

  2. 防止反射攻擊(無法通過反射創建新實例)。

  3. 防止反序列化破壞(枚舉天然支持?readResolve)。

缺點
  • 不能延遲加載(類加載時就初始化)。

  • 寫法稍特殊(部分開發者不習慣)。


3. 如何選擇單例模式?

實現方式線程安全延遲加載防止反射防止反序列化性能適用場景
餓漢式???????簡單場景
懶漢式(同步)?????不推薦
雙重檢查鎖???????高并發
靜態內部類???????推薦
枚舉???????最佳實踐

推薦選擇

  1. 簡單場景?→ 餓漢式。

  2. 需要延遲加載?→ 靜態內部類。

  3. 高并發 + 參數初始化?→ 雙重檢查鎖。

  4. 最佳實踐?→?枚舉單例(安全、簡潔)。


4. 單例模式的破壞與防御

4.1 反射攻擊

問題
Singleton instance1 = Singleton.getInstance();
// 反射調用私有構造方法
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance(); // 破壞單例
防御(非枚舉類)
private Singleton() {if (INSTANCE != null) {throw new RuntimeException("Use getInstance() method!");}
}

枚舉天然防御反射(JVM 保證唯一性)。


4.2 反序列化破壞

問題
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(Singleton.getInstance());
// 反序列化(會調用readObject,可能創建新實例)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) ois.readObject(); // 可能破壞單例
防御(非枚舉類)
// 添加readResolve方法,返回單例實例
protected Object readResolve() {return getInstance();
}

枚舉天然防御反序列化(JVM 保證唯一性)。


5. 總結

  1. 線程安全是關鍵:多線程環境下必須保證單例唯一。

  2. 推薦實現

    • 枚舉單例(簡潔、安全、防反射)。

    • 靜態內部類(延遲加載、高性能)。

    • 雙重檢查鎖(適合需要參數初始化的場景)。

  3. 避免

    • 不加鎖的懶漢式(線程不安全)。

    • 方法級同步懶漢式(性能差)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75178.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75178.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75178.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++ 環境設置

C++ 環境設置 引言 C++作為一種高性能的編程語言,廣泛應用于系統軟件、游戲開發、實時系統等領域。為了能夠順利進行C++編程,我們需要在計算機上配置合適的開發環境。本文將詳細講解如何在Windows、macOS和Linux系統中設置C++開發環境。 Windows系統下C++環境設置 1. 安裝…

【Kafka基礎】ZooKeeper在Kafka中的核心作用:分布式系統中樞神經系統

在分布式系統的世界里&#xff0c;協調和管理多個節點間的狀態是一項復雜而關鍵的任務。Apache Kafka作為一款高性能的分布式消息系統&#xff0c;其設計哲學是"專為單一目的而優化"——即高效處理消息流。為了實現這一目標&#xff0c;Kafka選擇將集群協調管理的重任…

<《AI大模型應知應會100篇》第8篇:大模型的知識獲取方式及其局限性

第8篇&#xff1a;大模型的知識獲取方式及其局限性 摘要 大模型&#xff08;如GPT、BERT、Qwen、DeepSeek等&#xff09;憑借其卓越的自然語言處理能力&#xff0c;已經成為人工智能領域的明星。然而&#xff0c;這些模型“知道”什么&#xff1f;它們如何獲取知識&#xff1f…

ESModule和CommonJS在Node中的區別

ESModule console.log(require);//>errorconsole.log(module);//>errorconsole.log(exports);//>errorconsole.log(__filename);//>errorconsole.log(__dirname);//>error全部報錯commonjs console.log(require);console.log(module);console.log(exports);co…

Spring Boot 配置文件加載優先級全解析

精心整理了最新的面試資料和簡歷模板&#xff0c;有需要的可以自行獲取 點擊前往百度網盤獲取 點擊前往夸克網盤獲取 Spring Boot 配置文件加載優先級全解析 Spring Boot 的配置文件加載機制是開發者管理不同環境配置的核心功能之一。其通過外部化配置&#xff08;Externaliz…

2025 年陜西消防設施操作員考試攻略:歷史文化名城的消防傳承與創新?

陜西擁有豐富的歷史文化遺產&#xff0c;眾多古建筑分布其中&#xff0c;同時也在不斷推進現代化建設&#xff0c;消防工作面臨傳承與創新的雙重任務&#xff0c;這在考試中也有所體現。? 考點融合與特色&#xff1a;一方面&#xff0c;古建筑的消防保護是重點&#xff0c;包…

【Unity網絡編程知識】C#的 Http相關類學習

1、搭建HTTP服務器 使用別人做好的HTTP服務器軟件&#xff0c;一般作為資源服務器時使用該方式&#xff08;學習階段建議使用&#xff09;自己編寫HTTP服務器應用程序&#xff0c;一般作為Web服務器或者短連接游戲服務器時使用該方式&#xff08;工作后由后端程序員來做&#…

Android Studio - 解決 Please Select Android SDK

一、出現的問題 點擊 Run 后彈窗&#xff0c;圖一位置出現圖二提示。 二、解決辦法 進入 Tools -> SDK Manager&#xff0c;在 Android SDK Location 點擊 Edit&#xff0c;一直 Next 就解決了。

UE5學習筆記 FPS游戲制作44 統一UI大小 sizeBox

如果我們希望多個類似的UI大小一樣&#xff0c;例如不同菜單的標題&#xff0c;可以使用sizeBox組件 我們在標題控件上&#xff0c;用sizeBox包裹所有子物體 然后指定他的最小寬高&#xff0c;或最大寬高 如果指定的是最小寬高&#xff0c;當子元素&#xff08;如圖片&#xf…

MCP協議介紹

MCP協議&#xff08;Model Context Protocol&#xff0c;模型上下文協議&#xff09;是由Anthropic公司推出的開放協議&#xff0c;旨在為AI大模型與外部數據源、工具之間建立標準化交互框架。其核心價值在于突破傳統API限制&#xff0c;通過統一接口實現AI與多源數據、工具的雙…

C#里使用WPF的MaterialDesignThemes

先要下載下面的包: <?xml version="1.0" encoding="utf-8"?> <packages><package id="MaterialDesignColors" version="5.2.1" targetFramework="net48" /><package id="MaterialDesignTheme…

基于 Spring Boot 瑞吉外賣系統開發(四)

基于 Spring Boot 瑞吉外賣系統開發&#xff08;四&#xff09; 新增分類 新增分類UI界面&#xff0c;兩個按鈕分別對應兩個UI界面 兩個頁面所需的接口都一樣&#xff0c;請求參數type值不一樣&#xff0c;type1為菜品分類&#xff0c;type2為套餐分類。 請求方法都為POST。…

神經網絡 | 基于脈沖耦合神經網絡PCNN圖像特征提取與匹配(附matlab代碼)

內容未發表論文基于脈沖耦合神經網絡(PCNN)的圖像特征提取與匹配研究 摘要 本文提出一種基于脈沖耦合神經網絡(Pulse-Coupled Neural Network, PCNN)的圖像特征提取與匹配方法。通過模擬生物視覺皮層神經元的脈沖同步發放特性,PCNN能夠有效捕捉圖像紋理與邊緣特征。實驗表…

LeetCode 252 會議室題全解析:Swift 實現 + 場景還原

文章目錄 摘要描述題解答案題解代碼分析示例測試及結果時間復雜度空間復雜度總結 摘要 在這篇文章中&#xff0c;我們將深入探討LeetCode第252題“會議室”的問題&#xff0c;提供一個用Swift編寫的解決方案&#xff0c;并結合實際場景進行分析。通過這篇文章&#xff0c;你將…

HBuilder運行uni-app程序報錯【Error: listen EACCES: permission denied 0.0.0.0:5173】

一、錯誤提示&#xff1a; 當使用HBuilder運行uni-app項目的時候提示了如下錯誤? 15:11:03.089 項目 project 開始編譯 15:11:04.404 請注意運行模式下&#xff0c;因日志輸出、sourcemap 以及未壓縮源碼等原因&#xff0c;性能和包體積&#xff0c;均不及發行模式。 15:11:04…

Flink框架:批處理和流式處理與有界數據和無界數據之間的關系

本文重點 從數據集的類型來看&#xff0c;數據集可以分為有界數據和無界數據兩種&#xff0c;從處理方式來看&#xff0c;有批處理和流處理兩種。一般而言有界數據常常使用批處理方式&#xff0c;無界數據往往使用流處理方式。 有界數據和無界數據 有界數據有一個明確的開始和…

虛擬列表react-virtualized使用(npm install react-virtualized)

1. 虛擬化列表 (List) // 1. 虛擬化列表 (List)import { List } from react-virtualized; import react-virtualized/styles.css; // 只導入一次樣式// 示例數據 const list Array(1000).fill().map((_, index) > ({id: index,name: Item ${index},description: This is i…

IT+開發+業務一體化:AI驅動的ITSM解決方案Jira Service Management價值分析(文末免費獲取報告)

本文來源atlassian.com&#xff0c;由Atlassian全球白金合作伙伴、DevSecOps解決方案提供商-龍智翻譯整理。 無論是支持內部員工、處理突發事件還是批準變更申請&#xff0c;服務團隊的每一分鐘都至關重要。您的企業是否做好了充分準備&#xff1f; 許多企業仍然依賴傳統的IT服…

leetcode刷題日記——167. 兩數之和 II - 輸入有序數組

[ 題目描述 ]&#xff1a; [ 思路 ]&#xff1a; 題目要求求數值numbers中的和為 target 的兩個數的下標最簡單的思路就是暴力求解&#xff0c;兩兩挨個組合&#xff0c;但時間復雜度為O(n2)&#xff0c;不一定能通過因為數組為非遞減&#xff0c;那我們可以使用雙指針&#…

【Leetcode-Hot100】盛最多水的容器

題目 解答 目的是求面積最大&#xff0c;面積是由兩個下標和對應的最小值得到&#xff0c;因此唯一的問題就是如何遍歷這兩個下標。我采用begin和end兩個變量&#xff0c;確保begin是小于end的&#xff0c;使用它們二者求面積&#xff0c;代碼如下&#xff1a; 很不幸 出錯了…