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

摘要? ? ? ??

????????在軟件開發的世界里,設計模式是開發者們智慧的結晶,它們為解決常見問題提供了經過驗證的通用方案。單例模式作為一種基礎且常用的設計模式,在許多場景中發揮著關鍵作用。本文將深入探討單例模式的定義、實現方式、應用場景以及可能面臨的問題與解決方案。

定義

????????單例模式的核心目標是保證一個類在整個系統中僅有一個實例存在,并且為系統提供一個訪問該實例的全局訪問點。這種模式在資源管理、數據共享等方面具有重要意義。例如,在一個數據庫連接管理系統中,為了避免頻繁創建和銷毀數據庫連接帶來的性能開銷,使用單例模式確保整個應用程序只有一個數據庫連接實例,所有對數據庫的操作都通過這個唯一的連接進行。

類圖

實現方式

餓漢式

????????餓漢式單例在類加載時就立即創建唯一的實例對象。代碼實現如下:

public class HungrySingleton {// 聲明并初始化唯一實例private static final HungrySingleton instance = new HungrySingleton();// 私有構造函數,防止外部實例化private HungrySingleton() {}// 提供全局訪問點public static HungrySingleton getInstance() {return instance;}
}

????????這種方式的優點是線程安全,因為實例在類加載階段就已創建,而類加載過程由 JVM 保證線程安全。同時,調用效率高,因為不需要額外的同步操作。然而,它的缺點是不能延遲加載。如果該單例對象占用資源較大,而在系統運行過程中可能很長時間都不會用到,那么這種提前創建實例的方式會造成資源浪費。

懶漢式

????????懶漢式單例是在第一次調用?getInstance?方法時才創建實例。代碼如下:

public class LazySingleton {// 聲明靜態實例,初始值為nullprivate static volatile LazySingleton instance = null;// 私有構造函數private LazySingleton() {}// 同步的獲取實例方法public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}

????????懶漢式的優勢在于可以延遲加載,只有在真正需要使用實例時才創建,避免了資源的過早占用。但是,由于?getInstance?方法使用了?synchronized?關鍵字進行同步,在多線程環境下,每次調用該方法都需要進行同步操作,這會導致調用效率不高。

雙重檢查鎖式

????雙重檢查鎖模式旨在解決單例、性能和線程安全問題。代碼如下:

public class LazyMan3 {private LazyMan3(){}private static volatile LazyMan3 instance;public static LazyMan3 getInstance(){//第一次判斷,如果instance不為null,不需要搶占鎖,直接返回對象if (instance == null){synchronized (LazyMan3.class){//第二次判斷if (instance == null){instance = new LazyMan3();}}}return instance;}
}
class LazyMan3Test{public static void main(String[] args) {LazyMan3 instance = LazyMan3.getInstance();LazyMan3 instance1 = LazyMan3.getInstance();System.out.println(instance == instance1);}
}

????????這種方式通過兩次?if?判斷,在第一次判斷實例不為?null?時,直接返回實例,避免了同步操作帶來的性能開銷。只有當實例為?null?時,才進入同步塊進行實例創建。然而,在多線程環境下,由于 JVM 會對實例化對象進行優化和指令重排序操作,可能會出現空指針問題。解決這個問題的方法是使用?volatile?關鍵字修飾?instance?變量,volatile?可以保證可見性和有序性,防止指令重排序導致的空指針異常。

靜態內部類式

????????靜態內部類式單例利用了類加載機制來實現線程安全和延遲加載。代碼如下:


public class LazyMan4 {private LazyMan4(){}//定義一個靜態內部類private static class LazyMan4Holder{private static final LazyMan4 INSYANCE = new LazyMan4();}//對外訪問方法public static LazyMan4 getInstance(){return LazyMan4Holder.INSYANCE;}
}
class LazyMan4Test{public static void main(String[] args) {LazyMan4 instance = LazyMan4.getInstance();LazyMan4 instance1 = LazyMan4.getInstance();System.out.println(instance == instance1);}
}

????????當外部類?LazyMan4?被加載時,其靜態內部類?LazyMan4Holder?并不會立即被加載。只有當調用?getInstance?方法時,LazyMan4Holder?才會被加載,此時會創建?LazyMan4?的唯一實例。這種方式保證了線程安全,因為類加載過程是線程安全的。同時,實現了延遲加載,提高了資源的利用效率。不過,與懶漢式類似,其調用效率相對不高。

靜態代碼塊式

????????靜態代碼塊式單例在靜態代碼塊中完成實例的初始化。代碼如下:

public class HungryChinese2 {//私有構造方法,為了不讓外界創建該類的對象private HungryChinese2(){}//聲明該類類型的變量private static HungryChinese2 hungryChinese2;//初始值為null//靜態代碼塊中賦值static {hungryChinese2 = new HungryChinese2();}//對外提供的訪問方式public static HungryChinese2 getInstance(){return hungryChinese2;}
}
class HungryChinese2Test{public static void main(String[] args) {HungryChinese2 instance = HungryChinese2.getInstance();HungryChinese2 instance1 = HungryChinese2.getInstance();System.out.println(instance.equals(instance1));}
}

????????這種方式與餓漢式類似,在類加載時通過靜態代碼塊創建實例,因此線程安全,但不能延遲加載。

枚舉式

????????枚舉式單例是一種簡潔且強大的實現方式。代碼如下:

public enum LazyMan5 {INSTANCE;
}
class LazyMan5Test{public static void main(String[] args) {LazyMan5 instance = LazyMan5.INSTANCE;LazyMan5 instance1 = LazyMan5.INSTANCE;System.out.println(instance == instance1);}
}

????????使用枚舉實現單例,不僅線程安全,調用效率高,而且天然地防止了反射和反序列化漏洞。在反序列化時,枚舉類型會保證返回的是已有的枚舉常量,而不會創建新的對象。不過,它同樣不能延遲加載。

應用場景

  1. 資源管理:如數據庫連接池、線程池等資源,使用單例模式可以確保整個系統中只有一個資源實例,避免資源的重復創建和浪費,提高資源的利用率和管理效率。

  2. 全局配置:系統的全局配置信息,如系統參數、環境變量等,使用單例模式可以方便地在整個系統中訪問和修改這些配置,保證配置的一致性。

  3. 日志記錄:日志記錄器通常使用單例模式,以便在整個應用程序中記錄日志信息。所有的日志記錄操作都通過同一個日志記錄器實例進行,方便管理和維護日志文件。

  4. 緩存管理:緩存系統可以使用單例模式來管理緩存實例,確保不同模塊對緩存的訪問和操作是一致的,提高緩存的命中率和性能。

單例模式可能面臨的問題及解決方案

Serializable問題

  • 如果單例類實現了?java.io.Serializable?接口,在反序列化時可能會出現問題。因為反序列化過程會創建一個新的對象,這可能導致多次反序列化同一對象時得到多個單例類的實例,破壞了單例模式的唯一性。解決方法是在單例類中添加?readResolve?方法:
private Object readResolve() throws ObjectStreamException {return instance;
}

????????這樣,在反序列化時,如果定義了?readResolve?方法,則直接返回此方法指定的對象,而不會創建新的對象,從而保證了單例的唯一性。

在Android 中使用單例模式可能會內存泄漏

????????在 Android 開發中,當單例類依賴于?Context?時,如果傳入的是?Activity?的?Context,可能會導致內存泄漏。例如:

public class CommUtils {private volatile static CommUtils mCommUtils;private Context mContext;public CommUtils(Context context) {mContext=context;}public static  CommUtils getInstance(Context context) {if (mCommUtils == null) {synchronized (CommUtils.class) {if (mCommUtils == null) {mCommUtils = new CommUtils(context);}}}return mCommUtils;}
}

????????只要這個單例沒有被釋放,那么持有該單例的?Activity?也不會被釋放,直到進程退出。為了解決這個問題,應盡量使用?Application?的?Context,因為?Application?的生命周期伴隨著整個進程的周期,不會因為某個?Activity?的銷毀而導致單例持有無效的?Context,從而避免內存泄漏。

總結

????????單例模式在軟件開發中具有廣泛的應用,不同的實現方式各有優劣。開發者需要根據具體的需求和場景,選擇合適的單例實現方式,同時注意解決可能出現的問題,以確保系統的高效、穩定運行。通過合理運用單例模式,可以提高代碼的可維護性、可擴展性和性能,為軟件項目的成功開發奠定堅實的基礎。

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

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

相關文章

基于FPGA頻率、幅度、相位可調的任意函數發生器(DDS)實現

基于FPGA實現頻率、幅度、相位可調的DDS 1 摘要 直接數字合成器( DDS ) 是一種通過生成數字形式的時變信號并進行數模轉換來產生模擬波形(通常為正弦波)的方法,它通過數字方式直接合成信號,而不是通過模擬信號生成技術。DDS主要被應用于信號生成、通信系統中的本振、函…

本地JAR批量傳私服

在有網絡隔離的環境下,Maven項目如果沒有搭建私服就得把用到的通用組件通過U盤在每個組員間拷貝來拷貝去。非常的麻煩跟低效。搭建私服,如果通用組件很多的時候手工一個一個上傳更是非常的麻煩跟低效; 我就遇上這問題,跟A公司合作…

【ROS實戰】02-ROS架構介紹

1. 簡介 你是否曾有過這樣的疑問:我按照文檔安裝了ROS,依照要求寫了一些示例節點(node)、消息(msg)和話題(topic),但覺得過程既麻煩又繁瑣。也許你開始懷疑:…

LeetCode算法題(Go語言實現)_07

題目 給你一個整數數組 nums,返回 數組 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘積 。 題目數據 保證 數組 nums之中任意元素的全部前綴元素和后綴的乘積都在 32 位 整數范圍內。 請 不要使用除法,且在 O(n) 時間復…

網絡華為HCIA+HCIP 網絡編程自動化

telnetlib介紹 telnetlib是Python標準庫中的模塊。它提供了實現Telnet功能的類telnetlib.Telnet。這里通過調用telnetlib.Telnet類里的不同方法實現不同功能。 配置云

查看GPU型號、大小;CPU型號、個數、核數、內存

GPU型號、大小 nvidia-smiCPU型號 cat /proc/cpuinfo | grep model name | uniqCPU個數 cat /proc/cpuinfo | grep "physical id" | uniq | wc -lCPU核數 cat /proc/cpuinfo | grep "cpu cores" | uniqCPU內存 cat /proc/meminfo | grep MemTotal參考…

Docker與K8S是什么該怎么選?

用了很久的容器化,最近突然看到一個問題問: docker和K8S究竟有什么區別,到底該怎么選?我認真思考了一會,發現一時間還真說不明白,于是就研究了一段時間發布今天的博文! Docker vs Kubernetes&a…

Android Handler 通過線程安全的 MessageQueue 和底層喚醒機制實現跨線程通信

目錄 一、MessageQueue 的線程安全實現 1. 消息隊列的同步鎖(synchronized) 2. 消息順序與延時處理 二、底層喚醒機制:從 Java 到 Linux 內核 1. 消息插入后的喚醒邏輯 2. Native 層實現(基于 Linux 的 eventfd 和 epoll&am…

關于 2>/dev/null 的作用以及機理

每個進程都有三個標準文件描述符:stdin(標準輸入)、stdout(標準輸出)和stderr(標準錯誤)。默認情況下,stderr會輸出到終端。使用2>可以將stderr重定向到其他地方,比如…

MySQL中的鎖機制:從全局鎖到行級鎖

目錄 1. 鎖的基本概念 2. 全局鎖 2.1 全局鎖的定義 2.2 全局鎖的類型 2.3 全局鎖的使用場景 2.4 全局鎖的實現方式 2.5 全局鎖的優缺點 2.6 全局鎖的優化 3. 表級鎖 3.1 表級鎖的類型 3.2 表級鎖的使用場景 3.3 表級鎖的優缺點 4. 意向鎖(Intention Lo…

編程語言選擇分析:C#、Rust、Go 與 TypeScript 編譯器優化

編程語言選擇分析:C#、Rust、Go 與 TypeScript 編譯器優化 在討論編程語言的選擇時,特別是針對微軟的 C# 和 Rust,以及谷歌的 Go 語言,以及微軟試圖通過 Go 來拯救 TypeScript 編譯器的問題,我們可以從多個角度來分析和…

基于WebRTC的嵌入式音視頻通話SDK:EasyRTC跨平臺兼容性技術架構實時通信的底層實現

EasyRTC的核心架構圍繞WebRTC技術構建,同時通過擴展信令服務、媒體服務器和NAT穿透機制,解決了WebRTC在實際部署中的痛點。其架構可以分為以下幾個核心模塊: 1)WebRTC基礎層 媒體捕獲與處理:通過getUserMediaAPI獲取…

【Rust】包和模塊管理,以及作用域等問題——Rust語言基礎15

文章目錄 1. 前言2. 包和 Crate3. 定義模塊以及模塊之間的關系4. 作用域問題4.1. 作用域問題初現4.2. 解決問題一4.3. 解決問題二4.4. super 關鍵字4.5. 將路徑引入作用域4.6. as 關鍵字4.7. pub use 重導出 5. 引入的問題5.1. 引入一個外部包5.2. 嵌套路徑來消除大量的 use 行…

微服務架構中的API網關:Spring Cloud與Kong/Traefik等方案對比

微服務架構中的API網關:Spring Cloud與Kong/Traefik等方案對比 一、API 網關的概念二、API 網關的主要功能2.1 統一入口與路由轉發2.2 安全與權限控制2.3 流量管理與容錯2.4 API 管理與聚合2.5 監控與日志2.5 協議轉換與適配2.6 控制平面與配置管理 三、API 網關選型…

NewStar CTF web wp

文章目錄 week1headach3會贏嗎智械危機謝謝皮蛋PangBai 過家家(1) week3include meblindsql1臭皮的計算機臭皮踩踩背這照片是你嗎 week4Pangbai過家家四blindsql2chocolateezcmsssezpollute隱藏的密碼 weeek5pangbai過家家(5)redissqlshell臭皮吹泡泡臭皮…

Linux驅動開發-①中斷②阻塞、非阻塞IO和異步通知

Linux驅動開發-①中斷②阻塞、非阻塞IO和異步通知 一,中斷1.中斷的流程2.上半部和下半部2.1上半部2.2下半部2.2.1 tasklet2.2.2 工作隊列 3.按鍵延時消抖中斷程序 二,阻塞和非阻塞IO和異步通知1.阻塞IO1.1 常見結構11.2 常見結構2 2.非阻塞IO2.1 驅動結構…

Docker和Dify學習筆記

文章目錄 1 docker學習1.1 基本命令使用1.1.1 docker ps查看當前正在運行的鏡像1.1.2 docker stop停止容器1.1.3 docker compose容器編排1.1.4 docker網絡[1] 進入到容器里面敲命令[2] docker network ls[3] brige網絡模式下容器訪問宿主機的方式 2 Dify的安裝和基礎使用2.1 下…

高并發庫存系統是否適合使用 ORM(Hibernate / MyBatis)

在設計高并發的庫存管理系統時,數據層的選擇至關重要。許多企業開發中習慣使用 ORM(如 Hibernate、MyBatis)來簡化數據庫訪問,但在高并發、高吞吐的場景下,ORM 的適用性往往成為爭議焦點。本文將探討高并發庫存系統是否…

Web爬蟲利器FireCrawl:全方位助力AI訓練與高效數據抓取。本地部署方式

開源地址:https://github.com/mendableai/firecrawl 01、FireCrawl 項目簡介 Firecrawl 是一款開源、優秀、尖端的 AI 爬蟲工具,專門從事 Web 數據提取,并將其轉換為 Markdown 格式或者其他結構化數據。 Firecrawl 還特別上線了一個新的功…

探秘Transformer系列之(16)--- 資源占用

探秘Transformer系列之(16)— 資源占用 文章目錄 探秘Transformer系列之(16)--- 資源占用0x00 概述0x01 背景知識1.1 數據類型1.2 進制&換算數字進制存儲度量換算 1.3 參數顯存占用有參數的層無參數的層所需資源 1.4 計算量 0…