設計模式——單例設計模式(創建型)

摘要

本文詳細介紹了單例設計模式,包括其定義、結構、實現方法及適用場景。單例模式是一種創建型設計模式,確保一個類只有一個實例并提供全局訪問點。其要點包括唯一性、私有構造函數、全局訪問點和線程安全。文章還展示了單例設計模式的類圖和時序圖,并介紹了三種實現方式:餓漢式、靜態內部類和枚舉方式。最后列舉了單例模式適合和不適合的場景,以及實戰建議和示例,如配置中心、統一 ID 生成器、日志收集器等。

1. 單例設計模式定義

單例模式是一種創建型設計模式,其目的是確保一個類只有一個實例,并提供一個全局訪問點來獲取該實例

通俗理解:

  • 單例模式就是讓一個類只創建一個對象,就像系統中只能有一個“總統”或“日志管理器”。
  • 這個類自己控制這個唯一實例的創建,并且其他類只能通過它提供的方法來獲取這個對象。

要點

說明

唯一性

類只能有一個實例

私有構造函數

禁止外部直接用 new創建對象

全局訪問點

提供一個靜態方法獲取該實例

線程安全(可選)

在多線程環境下仍能保持唯一性

2. 單例設計模式結構

2.1. 單例設計模式類圖

2.2. 單例設計模式時序圖

3. 單例設計模式實現方式

所有單例的實現都包含以下兩個相同的步驟:

  1. 將默認構造函數設為私有, 防止其他對象使用單例類的 new運算符。
  2. 新建一個靜態構建方法作為構造函數。 該函數會 “偷偷” 調用私有構造函數來創建對象, 并將其保存在一個靜態成員變量中。 此后所有對于該函數的調用都將返回這一緩存對象。

如果你的代碼能夠訪問單例類, 那它就能調用單例類的靜態方法。 無論何時調用該方法, 它總是會返回相同的對象。

實現方式

是否線程安全

是否懶加載

推薦程度

餓漢式

? 推薦(簡單可靠)

懶漢式(線程不安全)

? 不推薦

懶漢式 + synchronized

?? 有性能開銷

雙重檢查鎖(DCL)

? 推薦(兼顧性能)

靜態內部類

? 推薦(懶加載 + 安全)

枚舉方式

? 最推薦(防反射、反序列化)

3.1. 📍 餓漢式(推薦)

public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {} // 構造器私有化public static Singleton getInstance() {return instance;}
}

3.2. 📍 靜態內部類(推薦)

public class Singleton {private Singleton() {}private static class Holder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return Holder.INSTANCE;}
}

3.3. 📍 枚舉方式(最安全)

public enum Singleton {INSTANCE;public void doSomething() {System.out.println("do...");}
}

4. 單例設計模式適合場景

4.1. ? 單例模式適合的場景

場景類別

說明

示例

配置管理類

系統中讀取一次后多處使用,需全局共享

AppConfig.getInstance().get("db.url")

日志系統

全局統一記錄日志,防止多個文件或實例導致管理混亂

Logger.getInstance().log("...")

線程池 / 連接池

統一管理資源,避免重復創建、浪費連接

DbConnectionPool.getInstance().getConnection()

任務調度器

控制任務執行的唯一調度入口

TaskScheduler.getInstance().schedule(task)

唯一 ID 生成器

全局 ID 要求唯一,需中心化生成

IdGenerator.getInstance().nextId()

系統監控模塊

全局收集監控信息,避免多個統計點造成數據不一致

MetricsCollector.getInstance().record("qps", 5)

4.2. ? 單例模式不適合的場景

場景類別

問題描述

示例或說明

會話/用戶狀態類

多用戶或請求需獨立狀態,單例可能造成狀態串擾或數據混亂。

用戶登錄狀態、購物車信息等

多實例業務模型

業務本身設計要求一個類存在多個不同實例

訂單、交易、商品等

單元測試場景

單例難以隔離狀態,不利于并發測試和 mock。

單例殘留狀態會污染其他測試

生命周期綁定業務對象

對象需按請求或事務創建銷毀,單例不符合生命周期需求。

HTTP 請求上下文、數據庫事務上下文

狀態頻繁變化類

狀態共享會導致線程不安全,需加鎖處理復雜性上升。

非線程安全的緩存組件、計算任務執行狀態

需依賴注入管理的類

單例可能和 Spring 等框架的容器管理沖突,影響可測試性和解耦性。

建議用 Spring Bean 單例管理(@Component + @Scope

4.3. 🧠 單例設計模式實戰建議

使用場景

是否推薦使用單例

說明

配置類 / 常量類

? 是

全局唯一即可

Controller/Service

? 否

由 Spring 容器管理生命周期更合適

每個用戶/請求有狀態

? 否

應使用原型模式或線程隔離

工具類(無狀態)

?? 視情況

可以用 static 工具類替代

5. 單例設計模式實戰示例

在 Spring 項目中,單例模式(Singleton Pattern)使用場景非常廣泛。Spring 容器管理的 Bean 默認就是單例模式,它本質上滿足了單例設計模式的定義:“確保一個類只有一個實例,并且提供一個全局訪問點。所以在 Spring 項目中,我們一般直接使用 Spring 單例 Bean,既符合單例設計模式的定義,又簡化了開發和維護的復雜度。下面是一些常見 適合使用單例的場景 及其在 Spring 中的實現示例:

設計模式核心要求

Spring Bean 示例體現方式

唯一實例(Singleton)

Spring 容器中該 Bean 默認只實例化一次,所有注入該 Bean 的地方都共享同一個實例。

全局訪問點

通過 Spring 的依賴注入(@Autowired)或獲取 Bean 的方式,全局訪問同一個實例。

控制實例創建(防止多次 new)

不用 new 直接調用構造器,而是由 Spring 容器負責實例創建和生命周期管理。

線程安全(視具體實現而定)

需要保證成員變量線程安全,比如用線程安全的數據結構或無狀態設計。

5.1. ? 配置中心 / 配置管理器

使用場景: 需要在系統中讀取一次配置,供全局使用。

@Component
@Data
public class AppConfig {@Value("${app.env}")private String env;
}

使用方式:

@Service
public class MyService {@Autowiredprivate AppConfig appConfig;public void doSomething() {System.out.println(appConfig.getEnv());}
}

5.2. ? 統一 ID 生成器(如雪花算法)

@Component
public class IdGenerator {private final AtomicLong counter = new AtomicLong();public long nextId() {return counter.incrementAndGet();}
}

使用方式:

@Service
public class OrderService {@Autowiredprivate IdGenerator idGenerator;public void createOrder() {Long orderId = idGenerator.nextId();// 創建訂單邏輯}
}

5.3. ? 日志收集器 / 監控埋點上報器

@Component
public class MetricsCollector {public void record(String metric, int value) {// 上報指標邏輯System.out.println("metric: " + metric + " value: " + value);}
}

使用方式:

@Service
public class PaymentService {@Autowiredprivate MetricsCollector metricsCollector;public void pay() {// 業務邏輯metricsCollector.record("payment.count", 1);}
}

5.4. ? 緩存組件(輕量場景)

@Component
public class LocalCache {private final Map<String, Object> cache = new ConcurrentHashMap<>();public void put(String key, Object value) {cache.put(key, value);}public Object get(String key) {return cache.get(key);}
}

5.5. ? 線程池 / 異步任務執行器(通過 Spring 管理)

@Configuration
public class ThreadPoolConfig {@Beanpublic Executor taskExecutor() {return Executors.newFixedThreadPool(10);}
}

使用方式:

@Service
public class AsyncTaskService {@Autowiredprivate Executor taskExecutor;public void runAsyncTask() {taskExecutor.execute(() -> System.out.println("Running async task"));}
}

5.6. ? 策略工廠 / 狀態機容器

這些模式本質上也是通過單例注冊機制實現的,通常用 @Component + Map<String, Strategy> 組合來做策略路由。

@Component
public class StrategyFactory {private final Map<String, Strategy> strategies;public StrategyFactory(List<Strategy> strategyList) {strategies = new HashMap<>();for (Strategy s : strategyList) {strategies.put(s.getType(), s);}}public Strategy get(String type) {return strategies.get(type);}
}

5.7. ? Spring 項目中適合單例的場景

場景名稱

Spring 推薦實現

是否線程安全

配置類

@Component+ @Value

? 是

ID 生成器

@Component+ 原子類

? 是

日志/監控工具

@Component+ 線程安全方法

? 是

緩存組件

@Component+ ConcurrentMap

? 是(注意并發)

工具類

@Componentstatic工具類

?? 視情況

6. 單例設計模式思考

6.1. 為什么spring中對象天然是單例?

6.1.1. Spring 容器設計初衷

  • Spring 是一個IoC(控制反轉)容器,負責管理應用中的對象生命周期和依賴關系。
  • 容器初始化時,會根據配置(注解或 XML)創建并管理 Bean 實例。
  • 默認情況下,Spring 容器只會創建一個共享的 Bean 實例,供所有依賴該 Bean 的組件共享使用。

6.1.2. 單例 Bean 的定義和作用域

  • Spring 中的單例是指在 Spring 容器中只有一個實例,而不是 JVM 層面上的全局單例。
  • 默認作用域是 singleton,即:每個 Spring 容器中該 Bean 只有一個實例
  • 你可以通過 @Scope("prototype") 等其他作用域來改變默認行為。

6.1.3. Spring 單例實現機制(簡要)

  • 容器啟動時,會掃描并實例化所有單例 Bean。
  • 創建后,將實例放入一個單例緩存池(例如 singletonObjects)。
  • 當其他組件請求該 Bean 時,直接從緩存池取,避免重復創建。
  • 通過這種方式,Spring 確保每個 Bean 在容器內是唯一的。

6.1.4. 為什么默認使用單例?

  • 節省資源:不必每次調用都創建新實例,減少內存開銷。
  • 方便共享:多個組件可以共享狀態或行為一致的對象。
  • 生命周期管理:由容器統一管理,便于統一銷毀或初始化。
  • 線程安全的前提下,提高性能:一般單例 Bean 設計為無狀態或線程安全,避免多次實例化開銷。

6.1.5. 需要注意的點

  • Spring 的單例是“容器單例”,不同的 Spring 容器可以有不同的實例。
  • 如果使用多個容器或類加載器,則可能出現多實例。
  • 單例 Bean 設計時應注意線程安全,避免可變狀態帶來的并發問題。
  • 業務中有狀態的 Bean 一般不要用單例,使用 prototype 或其他作用域。

6.2. Spring 的單例是“容器單例”,不同的 Spring 容器可以有不同的實例。

意思是: Spring 單例不是 JVM 層面全局的單例,而是“每個 Spring 容器(ApplicationContext)中唯一的實例”。如果你項目里啟動了多個 Spring 容器(比如多個 ApplicationContext 實例),每個容器都會單獨創建自己的那個 Bean 實例。

舉例:

  1. 你有兩個 Web 應用,每個運行一個 Spring 容器,它們各自有自己的單例 Bean 實例。
  2. 或者你啟動了多個 Spring 容器做測試、隔離等,也會有多個實例。

6.3. 如果使用多個容器或類加載器,則可能出現多實例

類加載器(ClassLoader)不同,雖然類名相同,但被 JVM 認為是不同的類。因此,如果你在不同的類加載器中加載同一個類,也會導致出現“多個單例實例”,不是同一個對象。

典型場景:

  • Java EE 容器中不同的部署單元(war包、ear包)
  • 插件式架構、模塊化系統
  • 熱部署(熱更新)時重載類

6.4. 單例Bean設計時應注意線程安全,避免可變狀態帶來的并發問題

Spring 單例Bean是被多個線程共享的(特別是 Web 應用中,多個請求同時訪問)。如果單例 Bean 內部有可變的成員變量,就會有線程安全風險,可能導致數據錯亂或異常。

設計原則:

  • 無狀態設計: Bean 不保存業務狀態,所有狀態通過方法參數傳遞。
  • 線程安全的數據結構: 比如使用 ConcurrentHashMapAtomicInteger
  • 同步控制: 必要時用鎖、synchronized 保證并發安全。

6.5. 業務中有狀態的 Bean 一般不要用單例,使用 prototype 或其他作用域

有狀態 Bean:保存用戶會話、操作數據等狀態的 Bean。用單例的話,狀態被多個線程共享,會導致狀態混亂和并發問題。這時應使用 Spring 的其他作用域,比如:

  • prototype:每次請求都會創建新實例,避免共享狀態。
  • request(Web作用域):每個 HTTP 請求一個實例。
  • session:每個用戶會話一個實例。

博文參考

  • 5. 單例模式 — Graphic Design Patterns
  • 單例設計模式
  • 創建型 - 單例模式(Singleton pattern) | Java 全棧知識體系

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

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

相關文章

Lyra學習筆記 Experience流程梳理

目錄 前言1 創建2 加載3 Deactivate4 總結與圖示 前言 這篇主要將視角放在Experience的流程&#xff0c;所以不會涉及一些更深的東西 之后ULyraExperienceManagerComponent簡稱為EMC 1 創建 完事開頭難&#xff0c;首先找到了管理Experience的組件&#xff0c;那么它的初始化…

Ubuntu下編譯mininim游戲全攻略

目錄 一、安裝mininim 軟件所依賴的庫&#xff08;重點是allegro游戲引擎庫&#xff09;二、編譯mininim 軟件三、將mininim打包給另一個Ubuntu系統使用四、安卓手機運行mininim 一、安裝mininim 軟件所依賴的庫&#xff08;重點是allegro游戲引擎庫&#xff09; 1. 用apt-get…

SMT貼片制造流程關鍵環節解析

內容概要 現代電子制造領域中&#xff0c;SMT&#xff08;表面貼裝技術&#xff09;作為核心工藝&#xff0c;其流程的精密性與穩定性直接決定產品性能與生產良率。本文以SMT貼片制造流程為主線&#xff0c;系統解析焊膏印刷、元器件貼裝、回流焊接三大核心工藝的技術要點。其…

HTTP/2與HTTP/3特性詳解:為你的Nginx/Apache服務器開啟下一代Web協議

更多服務器知識&#xff0c;盡在hostol.com 嘿&#xff0c;各位站長和服務器管理員朋友們&#xff01;咱們天天跟網站打交道&#xff0c;都希望自己的網站能像火箭一樣快&#xff0c;用戶體驗“嗖嗖”的。但你知道嗎&#xff1f;除了服務器硬件配置、代碼優化、CDN加速這些“常…

pytest 常見問題解答 (FAQ)

pytest 常見問題解答 (FAQ) 1. 基礎問題 Q1: 如何讓 pytest 發現我的測試文件&#xff1f; 測試文件命名需符合 test_*.py 或 *_test.py 模式測試函數/方法需以 test_ 開頭測試類需以 Test 開頭(且不能有__init__方法) Q2: 如何運行特定測試&#xff1f; pytest path/to/t…

【前端】SPA v.s. MPA

鏈接&#xff1a;頁面結構 誤區 頁面結構管理有兩種常見方式&#xff1a;路由形式 和 組件形式。路由形式 對應MPA &#xff0c;組件形式對應SPA ? 誤區 1&#xff1a;路由形式 MPA? 路由是 SPA 和 MPA 共有的概念&#xff0c;區別在于路由映射的對象&#xff1a; MPA 的…

Matlab數據類型

本篇介紹我在南農matlab課程上的所學&#xff0c;我對老師ppt上的內容重新進行了整理并且給出代碼案例。主要內容在矩陣。如果真的想學matlab&#xff0c;我不認為有任何文檔能夠超過官方文檔&#xff0c;請移步至官網&#xff0c;本篇說實話只是寫出來給自己和學弟學妹作期末復…

代碼隨想錄算法訓練營 Day58 圖論Ⅷ 拓撲排序 Dijkstra

圖論 題目 117. 軟件構建 拓撲排序&#xff1a;給出一個有向圖&#xff0c;把這個有向圖轉成線性的排序就叫拓撲排序。 當然拓撲排序也要檢測這個有向圖是否有環&#xff0c;即存在循環依賴的情況&#xff0c;因為這種情況是不能做線性排序的。所以拓撲排序也是圖論中判斷有向…

vscode中launch.json、tasks.json的作用及實例

文章目錄 launch.json是什么作用多環境調試簡單實例進階使用核心配置項解析調試第三方程序 launch.json是什么 顧名思義&#xff1a;它是在.vscode文件夾下的launch.json&#xff0c;所以是vscode啟動調試的配置文件。總結&#xff1a;通過定義調試參數、環境變量和啟動方式&a…

NeRF PyTorch 源碼解讀 - 體渲染

文章目錄 1. 體渲染公式推導1.1. T ( t ) T(t) T(t) 的推導1.2. C ( r ) C(r) C(r) 的推導 2. 體渲染公式離散化3. 代碼解讀 1. 體渲染公式推導 如下圖所示&#xff0c;渲染圖像上點 P P P 的顏色值 c c c 是累加射線 O P → \overrightarrow{OP} OP 在近平面和遠平面范圍…

標題:2025海外短劇爆發年:APP+H5雙端系統開發,解鎖全球流量與變現新大陸

描述&#xff1a; 2025年出海新風口&#xff01;深度解析海外短劇系統開發核心&#xff08;APPH5雙端&#xff09;&#xff0c;揭秘高效開發策略與商業化路徑&#xff0c;助您搶占萬億美元市場&#xff01; 全球娛樂消費模式正在劇變。2025年&#xff0c;海外短劇市場已從藍海…

React JSX語法介紹(JS XML)(一種JS語法擴展,允許在JS代碼中編寫類似HTML的標記語言)Babel編譯

在線調試網站&#xff1a;https://zh-hans.react.dev/learn 文章目錄 JSX&#xff1a;現代前端開發的聲明式語法概述JSX的本質與工作原理什么是JSXJSX轉換流程 JSX語法特性表達式嵌入&#xff08;JSX允許在大括號內嵌入任何有效的JavaScript表達式&#xff09;屬性傳遞&#xf…

Unity UI系統中RectTransform詳解

一、基礎代碼示例 public GameObject node; var rect node.GetComponent<RectTransform>();Debug.Log($"anchoredPosition----{rect.anchoredPosition}"); Debug.Log($"offsetMin.x--{rect.offsetMin}"); Debug.Log($"offsetMax.x--{rect.of…

【數據庫】并發控制

并發控制 在數據庫系統&#xff0c;經常需要多個用戶同時使用。同一時間并發的事務可達數百個&#xff0c;這就是并發引入的必要性。 常見的并發系統有三種&#xff1a; 串行事務執行&#xff08;X&#xff09;&#xff0c;每個時刻只有一個事務運行&#xff0c;不能充分利用…

我們來學mysql -- “數據備份還原”sh腳本

數據備份&還原 說明執行db_backup_cover.sh腳本 說明 環境準備&#xff1a;來源數據庫(服務器A)&#xff1b;目標數據庫(服務器B)dbInfo.sh腳本記錄基本信息 來源庫、目標庫的ip、port及執行路徑 # MySQL 客戶端和 mysqldump 的路徑 MYSQL_CLIENT"/work/oracle/mysql…

【NLP 78、手搓Transformer模型結構】

你以為走不出的淤泥&#xff0c;也遲早會云淡風輕 —— 25.5.31 引言 ——《Attention is all you need》 《Attention is all you need》這篇論文可以說是自然語言處理領域的一座里程碑&#xff0c;它提出的 Transformer 結構帶來了一場技術革命。 研究背景與目標 在 Transfo…

深入理解CSS常規流布局

引言 在網頁設計中&#xff0c;理解元素如何排列和相互作用至關重要。CSS提供了三種主要的布局方式&#xff1a;常規流、浮動和定位。本文將重點探討最基礎也是最常用的常規流布局&#xff08;Normal Flow&#xff09;&#xff0c;幫助開發者掌握頁面布局的核心機制。 什么是…

樹結構詳細介紹(javascript版)

樹結構的基本概念 樹是一種非線性數據結構&#xff0c;由節點和連接節點的邊組成。與線性數據結構&#xff08;如數組、鏈表&#xff09;不同&#xff0c;樹具有層次結構&#xff0c;非常適合表示有層次關系的數據。 樹的基本術語 節點 (Node)&#xff1a; 樹中的基本單元&a…

element-plus bug整理

1.el-table嵌入el-image標簽預覽時&#xff0c;顯示錯亂 解決&#xff1a;添加preview-teleported屬性 <el-table-column label"等級圖標" align"center" prop"icon" min-width"80"><template #default"scope"&g…

RabbitMQ和MQTT區別與應用

RabbitMQ與MQTT深度解析&#xff1a;協議、代理、差異與應用場景 I. 引言 消息隊列與物聯網通信的重要性 在現代分布式系統和物聯網&#xff08;IoT&#xff09;生態中&#xff0c;高效、可靠的通信機制是構建穩健、可擴展應用的核心。消息隊列&#xff08;Message Queues&am…