初識單例模式

文章目錄

    • 場景通點
    • 定義
      • 實現思路
    • 六種 Java 實現
      • 餓漢式
      • 懶漢式
      • synchronized 方法
      • 雙重檢查鎖 Double Check Lock + Volatile
      • 靜態內部類 Singleton Holder
      • 枚舉單例
    • 單例運用場景
    • 破解單例模式
    • 參考

場景通點

  • 資源昂貴:數據庫連接池、線程池、日志組件,只需要一份全局對象,頻繁 new 會導致資源浪費。
  • 全局一致性:配置中心、緩存目錄、全局計數器等需要強唯一,避免狀態錯亂。
  • 集中管理:方便做“生命周期”管控(初始化、銷毀)。
  • 日志、配置中心、線程池、連接池、注冊表、全局序列號生成器等 無狀態或少狀態、需要唯一性的組件
  • 資源較重而復用頻繁

定義

保證一個類只有一個實例,并提供一個全局訪問點

  • “只有一個”? 私有構造器 + 類級別的持有者(靜態字段)
  • “全局訪問”? 公共靜態方法 (getInstance())

實現思路

  • 靜態化實例對象, 讓實例對象與 Class 對象互相綁定, 通過 Class 類對象就可以直接訪問
  • 私有化構造方法, 禁止通過構造方法創建多個實例 —— 最重要的一步
  • 提供一個公共的靜態方法, 用來返回這個類的唯一實例

六種 Java 實現

#代碼量懶加載線程安全可靠程度說明
1餓漢式(Eager)??★★★類加載即實例化,簡單直觀,無法延遲加載。
2懶漢式(Lazy-非線程安全)??只示范學習,生產別用。
3synchronized 方法??★★簡單但性能差,鎖整個方法。
4DCL + volatile??★★★雙重檢查鎖 (Double-Checked Locking);JDK 5+ 才完全安全。
5靜態內部類??★★★★JVM 類加載天生線程安全,推薦。
6枚舉單例??★★★★★反序列化 / 反射天然防護,極簡,強烈推薦

餓漢式

public final class EagerSingleton {private static final EagerSingleton INSTANCE = new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() { return INSTANCE; }
}
  • 優點:實現最簡單、天然線程安全

Java 的語義包證了在引用這個字段之前并不會初始化它, 并且訪問這個字段的任何線程都將看到初始化這個字段所產生的所有寫入操作.

  • 缺點:類加載就占用內存;若實例創建開銷大或很少用到,會浪費。

為什么餓漢式中類加載占內存?

步驟發生位置說明
① 類加載(Loading)ClassLoader把 .class 字節流讀進內存,創建 Class 對象,本身幾乎不耗多少內存。
② 鏈接 → 初始化JVM 執行 鏈接(驗證、準備、解析)后,進入 初始化 階段所有 static 字段<clinit> 方法里按源代碼順序賦值。
餓漢式把單例對象定義成 private static final XXX INSTANCE = new XXX();
—— 這一行在 初始化階段立即 new 對象
③ 對象駐留Java 堆無論業務代碼是否真的使用過 getInstance(),對象已被創建并常駐堆中,直到類被卸載或進程結束。
  • “占內存”指的是 單例實例 已經分配在堆里,而不是 Class 元數據。
  • 若實例本身很大(例如預加載 MB 級的配置或字典),但應用啟動后很久才用到,就屬于“浪費”。
  • 餓漢式是典型的以空間換時間思想的實現: 不用判斷就直接創建, 但創建之后如果不使用這個實例, 就造成了空間的浪費. 雖然只是一個類實例, 但如果是體積比較大的類, 這樣的消耗也不容忽視.

懶漢式

線程不安全

public final class LazySingletonUnsafe {private static LazySingletonUnsafe instance;private LazySingletonUnsafe() {}public static LazySingletonUnsafe getInstance() {if (instance == null) {                      // ①instance = new LazySingletonUnsafe();    // ②}return instance;}
}
  • 優點:節省空間, 用到的時候再創建實例對象

為什么多線程下會出問題?

競態點:假設 T1 與 T2 同時進入方法,instance 仍為 null

  • T1 通過檢查(①)進入 ②,開始執行 new,尚未完成。
  • T2 同樣看到 instance == null,也執行 new
  • 結果:生成兩個實例,違背單例約束

問題原因:沒有同步手段(鎖、volatile + CAS 等)來保證檢查與創建的 原子性

測試案例:

final class LazySingleton {private static LazySingleton instance = null;private LazySingleton() {}public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}public class demo {public static void main(String[] args) {Set<String> instanceSet = Collections.synchronizedSet(new HashSet<>());for (int i = 0; i < 1000; i++) {new Thread(() -> {instanceSet.add(LazySingleton.getInstance().toString());}).start();}for (String instance : instanceSet) {System.out.println(instance);}}
}

輸出結果:

如果輸出的結果中有 2 個或 2 個以上的對象, 就足以說明在并發訪問的過程中出現了線程安全問題

LazySingleton@668916a0
LazySingleton@c23df88

synchronized 方法

這樣的做法對所有線程的訪問都會進行同步操作, 有很嚴重的性能問題

public final class SynchronizedSingleton {private static SynchronizedSingleton instance;private SynchronizedSingleton() {}public static synchronized SynchronizedSingleton getInstance() {if (instance == null) {instance = new SynchronizedSingleton();}return instance;}
}

雙重檢查鎖 Double Check Lock + Volatile

public final class DCLSingleton {private static volatile DCLSingleton instance;private DCLSingleton() {}public static DCLSingleton getInstance() {// 先判斷實例是否存在if (instance == null) {// 加鎖創建實例synchronized (DCLSingleton.class) {// 再次判斷, 因為可能出現某個線程拿了鎖之后, 還沒來得及執行初始化就釋放了鎖,// 而此時其他的線程拿到了鎖又執行到此處 ==> 這些線程都會創建一個實例, 從而創建多個實例對象if (instance == null) {instance = new DCLSingleton();}}}return instance;}
}
  • 為什么要雙重檢查
  • volatile 關鍵詞有什么作用

使用雙重檢查的原因:

  • 第一次檢查:絕大部分時間單例已存在,快速返回,避免進入 synchronized,性能開銷 ≈0
  • 第二次檢查:只有在第一次判斷為 null、并且當前線程拿到鎖時才進入;此時仍需再判一次,防止 “T1 創建 →T2 等鎖 →T2 再創建” 的并發漏洞。(就是剛才懶漢式導致的問題)

volatile 關鍵詞:

  • 可見性,保證線程中對這個變量所做的任何寫入操作對其他線程都是即時可見的,寫入 instance 對所有線程立刻可見
  • 禁止 JVM 指令重排:對象創建過程實際上分三步(并不是原子性操作)
a. 分配內存,在堆內存中, 為新的實例開辟空間
b. 調用構造器初始化
c. 將引用賦給變量 (instance = address)

CPU 和編譯器可能把 b、c 重排成 c→b。可能發生以下情況:

  • T1 執行到 c,引用已非 null,但對象尚未初始化;
  • T2 讀取到“非 null”便返回,使用到的是 半成品對象

volatile 在 Java 內存模型中加了寫后讀屏障,保證初始化完成先于賦值,徹底避免重排風險。

靜態內部類 Singleton Holder

只有在第一次調用 getInstance() 時才被加載,JVM 類加載保證線程安全 ? 懶加載 + 免鎖

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

JVM 記載類的時候有以下步驟:① 加載 -> ② 驗證 -> ③ 準備 -> ④ 解析 -> ⑤ 初始化

JVM 在加載外部類的過程中, 是不會加載靜態內部類的, 只有內部類(SingletonHolder)的屬性/方法被調用時才會被加載, 并初始化其靜態屬性(instance)

為什么會將創建實例放在靜態內部?

核心機制:Initialization-on-demand holder idiom

  • 懶加載
    • 外部類 HolderSingleton 被加載時,并不會立即加載 Holder
    • 只有首次調用 getInstance(),JVM 才會解析對 Holder.INSTANCE 的主動使用,從而 加載并初始化 Holder,此時才 new 實例
  • 線程安全(不需要鎖)
    • JVM 對 類初始化 有“互斥保證”:同一個類的 <clinit> 在多線程環境中只會執行一次,且執行期間其他線程會被阻塞。
    • 實例創建天然是原子且線程安全的
  • 零額外開銷
    • 不需要 synchronized、不需要 volatile;除第一次觸發外,沒有任何同步損耗

具體原因:靜態內部類利用了 類的按需加載 + 初始化互斥,同時滿足“懶加載 + 線程安全 + 高性能”

枚舉單例

public enum EnumSingleton {INSTANCE;// 可添加字段/方法private final Map<String, String> cache = new ConcurrentHashMap<>();public void put(String k, String v) { cache.put(k, v); }public String get(String k) { return cache.get(k); }
}
  • JVM 保證 序列化安全:枚舉反序列化時不會新建實例。
  • 防反射:任何試圖通過 Constructor.newInstance 創建都會拋 IllegalArgumentException
  • 代碼最少,可自然支持 switch

單例運用場景

框架/庫場景實現方式
JDKjava.lang.Runtime餓漢式
Log4j / LogbackLoggerContext懶加載 + 雙檢鎖
Spring默認 Bean Scope = singleton容器級單例,非 GoF 模式
MyBatisSqlSessionFactoryBuilder ? SqlSessionFactory通常一個全局實例
HikariCPHikariPool 內部維護線程安全單例連接池

破解單例模式

  • 除枚舉方式外, 其他方法都會通過反射的方式破壞單例,因此可以在構造方法中進行判斷 —— 若已有實例, 則阻止生成新的實例
private Singleton() throws Exception {if (instance != null) {throw new Exception("Singleton already initialized, 此類是單例類, 不允許生成新對象, 請通過getInstance()獲取本類對象");}
}
  • 如果單例類實現了序列化接口 Serializable, 就可以通過反序列化破壞單例,因此可以不實現序列化接口, 或者重寫反序列化方法 readResolve()
// 反序列化時直接返回當前實例
public Object readResolve() {return instance;
}
  • Object#clone() 方法也會破壞單例, 即使你沒有實現 Cloneable 接口 —— 因為 clone()方法是 Object 類中的,需要重寫方法并拋出異常

參考

  • 設計模式 - Java 中單例模式的 6 種寫法及優缺點對比 - 瘦風 - 博客園

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

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

相關文章

音樂搶單源碼(連單卡單/疊加組規則/打針/多語言)

簡介&#xff1a; 測試環境&#xff1a;Nginx、PHP7.2、MySQL5.6&#xff0c;運行目錄設置為public&#xff0c;偽靜態thinkphp&#xff0c;建議開啟SSL 測試語言&#xff1a;11種 不知道誰給我的一套&#xff0c;說是買來的&#xff0c;我看了一下功能感覺也一般&#…

分類樹查詢性能優化:從 2 秒到 0.1 秒的技術蛻變之路

在電商系統中&#xff0c;分類樹查詢是一個基礎且高頻的功能&#xff0c;然而這個看似簡單的功能背后卻隱藏著不小的性能挑戰。本文將分享我們在實際項目中對分類樹查詢功能進行五次優化的全過程&#xff0c;看如何將查詢耗時從 2 秒縮短至 0.1 秒&#xff0c;為用戶提供更流暢…

Ansible 介紹及安裝

簡介 Ansible 是一款開源的自動化工具&#xff0c;廣泛應用于配置管理、應用部署、任務自動化以及多節點管理等領域。它由 Michael DeHaan 于 2012 年創建&#xff0c;ansible 目前已經已經被紅帽官方收購&#xff0c;是自動化運維工具中大家認可度最高的&#xff0c;并且上手…

超光譜相機的原理和應用場景

超光譜相機是光譜成像技術的尖端形態&#xff0c;具備亞納米級光譜分辨率與超千波段連續覆蓋能力&#xff0c;通過“圖譜合一”的三維數據立方體實現物質的精準識別與分析。其核心技術架構、應用場景及發展趨勢如下&#xff1a;一、核心技術原理1、?分光機制??干涉分光?&am…

掌握MySQL函數:高效數據處理指南

? 在 MySQL 數據庫管理系統中&#xff0c;函數扮演著極為重要的角色。它們就像是數據庫操作的得力助手&#xff0c;能夠幫助開發者高效地完成各種數據處理任務。本文將深入探討 MySQL 函數的方方面面&#xff0c;從其基本概念到實際應用&#xff0c;幫助讀者全面掌握這一強大的…

10.SpringBoot的統一異常處理詳解

文章目錄1. 異常處理基礎概念1.1 什么是異常處理1.2 為什么需要統一異常處理1.3 Spring異常處理機制2. SpringBoot默認異常處理2.1 默認錯誤頁面2.2 自定義錯誤頁面3. 全局異常處理器3.1 基礎全局異常處理器3.2 統一響應格式3.3 使用統一響應格式的異常處理器4. 自定義異常4.1 …

No Hack No CTF 2025Web部分個人WP

No Hack No CTF 2025 Next Song is 春日影 hint&#xff1a;NextJS Vulnerability at /adminCVE-2025-29927Next.js 中間件權限繞過漏洞 訪問admin路由發現跳轉利用CVE&#xff1a; curl -i \-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:m…

STM32第十八天 ESP8266-01S和電腦實現串口通信

一&#xff1a; ESP和電腦實現串口通信1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 響應 : OK 2. 連接路路由器? ATCWJAP"SSID","password" // SSID and password of router 響應 : OK 3. 查詢 ESP8266 設備的 IP 地址 ATCIFSR 響應 : CIFSR:APIP…

STM32第十七天ESP8266-01Swifi模塊

ESP8266-01S wifi模塊1&#xff1a;ESP8266是實現wifi通訊的一個模塊種類&#xff0c;有很多分類包含esp8266-12、esp8266-12E、ESP8266-01S、esp32等等。esp8266-01S由一顆esp8266作為主控再由一塊flash作為存儲芯片組成&#xff0c;帶有板載芯片供電采用3.3V電壓使用串口進行…

ProCCD復古相機:捕捉復古瞬間

在數字攝影盛行的今天&#xff0c;復古膠片相機的獨特質感和懷舊風格依然吸引著眾多攝影愛好者。ProCCD復古相機APP正是這樣一款能夠滿足用戶對復古攝影需求的應用程序。它通過模擬復古CCD數碼相機的效果&#xff0c;讓用戶在手機上也能輕松拍出具有千禧年風格的照片和視頻。無…

Spring Boot 應用啟動時,端口 8080 已被其他進程占用,怎么辦

1、修改application.yml配置文件&#xff0c;將端口號更改為未被占用的端口&#xff08;例如9090&#xff09;2、以管理員身份運行命令提示符在命令提示符窗口中輸入命令netstat -ano | findstr :8080”輸出結果可能如下&#xff1a;“TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING xx…

使用Jenkins完成springboot項目快速更新

?重磅&#xff01;盹貓的個人小站正式上線啦&#xff5e;誠邀各位技術大佬前來探秘&#xff01;? 這里有&#xff1a; 硬核技術干貨&#xff1a;編程技巧、開發經驗、踩坑指南&#xff0c;帶你解鎖技術新姿勢&#xff01;趣味開發日常&#xff1a;代碼背后的腦洞故事、工具…

HDLBits刷題筆記和一些拓展知識(九)

文章目錄HDLBits刷題筆記CircuitsFsm1Fsm1sFsm2Fsm3onehotExams/ece241 2013 q4Lemmings1Lemmings2Lemmings3Lemmings4Fsm onehotFsm ps2Fsm ps2dataFsm serialFsm serialdataFsm serialdpFsm hdlc未完待續HDLBits刷題筆記 以下是在做HDLBits時的一些刷題筆記&#xff0c;截取一…

CD46.【C++ Dev】list的模擬實現(1)

目錄 1.STL庫的list 2.模擬實現 節點結構體 list類 無參構造函數 尾插函數 迭代器★ begin() operator 前置 后置 operator-- 前置-- 后置-- operator! operator end() operator* const修飾的迭代器的設計 1.STL庫的list 模擬實現list之前,先看看STL庫里的…

數據結構——二叉樹的基本介紹

————————————本文旨在討論與學習計算機知識&#xff0c;歡迎交流————————————上一章&#xff0c;我們講解了樹結構的綜述導論&#xff0c;那么&#xff0c;現在我們來深入了解一下樹結構中最常用研究的結構——二叉樹結構&#xff08;上一章的擴展——…

英偉達發布 Llama Nemotron Nano 4B:專為邊緣 AI 和科研任務優化的高效開源推理模型

英偉達推出了 Llama Nem)otron Nano 4B&#xff0c;這是一款專為在科學任務、編程、符號運算、函數調用和指令執行方面提供強大性能與效率而設計的開源推理模型&#xff0c;其緊湊程度足以支持邊緣部署。該模型僅包含 40 億參數&#xff0c;卻在內部基準測試中實現了比其他多達…

論文閱讀筆記——Autoregressive Image Generation without Vector Quantization

MAR 論文 基于 VQ&#xff08;向量量化&#xff09;的圖像生成方法具有顯著優勢&#xff0c;它通過離散化壓縮將原始圖像映射到有限的 codebook 空間&#xff0c;從而縮小學習范圍、降低建模難度&#xff0c;同時這種離散表示更易于與自回歸&#xff08;AG&#xff09;生成方式…

【科普】關于C 語言日志系統實戰:如何同時輸出到終端和文件?

1.概述 c語言沒有現成的日志庫&#xff0c;如果要記錄日志&#xff0c;需要自己封裝一個日志庫。如果要實現日志級別和參數打印&#xff0c;還是比較麻煩的&#xff0c;正好在github找到了一個c語言開源日志庫&#xff0c;可以實現日志級別打印&#xff0c;參數打印&#xff0…

2025,數字人借直播場景邁過“真假線”丨數智化觀察

作者 | 曾響鈴文 | 響鈴說一夜帶貨超5500萬GMV、觀看人次1300萬&#xff0c;羅永浩數字人在百度電商的直播首秀正在掀起新的行業浪潮——2025&#xff0c;數字人直播帶貨成功出圈&#xff0c;加速進入大眾視野&#xff0c;被更多的消費者所認可。成就這場熱潮的關鍵點之一&…

HTML表格導出為Excel文件的實現方案

1、前端javascript可通過mime類型、blob對象或專業庫&#xff08;如sheetjs&#xff09;實現html表格導出excel&#xff0c;適用于中小型數據量&#xff1b;2、服務器端方案利用后端語言&#xff08;如python的openpyxl、java的apache poi&#xff09;處理復雜報表和大數據&…