詳解單例模式、模板方法及項目和源碼應用

大家好,我是此林。

設計模式為解決特定問題提供了標準化的方法。在項目中合理應用設計模式,可以避免重復解決相同類型的問題,使我們能夠更加專注于具體的業務邏輯,減少重復勞動。設計模式在定義系統結構時通常考慮到未來的擴展。例如,工廠模式、策略模式等能讓系統在增加新功能時無需改動現有代碼,只需擴展新模塊即可,減少了修改現有代碼的風險。

今天分享的是單例模式和模板模式,這兩種設計模式在項目和源碼中的使用。

1. 單例模式

一般開發中,我們使用的是 Spring 框架,默認情況下,我們通過 @Bean@Component 等注解注入的 Bean 對象是 單例的(即 Singleton),也就是說?Spring 會在容器啟動時創建一個該類型的 Bean 實例,并在整個應用程序上下文中共享這個實例。可以通過 @Scope 注解來指定 Bean 的作用域,控制其生命周期和作用范圍。默認的作用域是 @Scope("singleton")。

那 Spring 是如何實現單例模式的呢?

關注源碼,發現 Spring 維護了一個全局的單例池(ConcurrentHashMap),key 是 BeanName,value 是 Bean 對象。
DefaultSingletonBeanRegistry 類實現了 SingletonBeanRegistry 接口)

我們在開發過程中使用Bean對象,會根據 BeanName 去單例池中獲取 Bean 對象,保證了對象的全局的唯一性。

當然,它和我們平時所說的幾種單例模式實現還是不一樣的。

1. 餓漢式

public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getSingleton() {return instance;}
}

關鍵點:

1. 使用 private 關鍵字,代表外部無法對變量?instance 直接修改

2. 使用 static 關鍵字,代表?instance 變量在類加載的時候就會被初始化,這個和 JVM 加載類有關。

3. final 關鍵字的作用

  • final 用于修飾變量時,表示該變量 一旦賦值就不能再被修改。也就是說,變量 引用 一旦指向某個對象,就不能再指向其他對象。
  • final 修飾一個引用變量時,它指向的對象不能改變。但是,引用的對象本身是可以改變的,也就是 對象內部的狀態是可以修改的

4. 私有化構造方法。也就是防止外部通過 new 關鍵字創建多個實例。

5. 最后一個 getSingleton() 方法是提供全局訪問點,返回唯一實例。

6.?在類加載時就初始化實例,避免線程安全問題。缺點是無論是否使用該實例,都會創建一個實例,浪費內存資源。

2. 雙重檢查鎖定(懶加載)

public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getSingleton() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}   }

關鍵點:

1. 由于?instance 用了 static 修飾(類級別的變量),且沒有初始化,那么類加載的時候 instance 賦值為 null。

2. 不加 final ,是因為后續 instance 需要的時候會被賦值;如果加了 final ,那么 instance 永遠只能指向 null。當然哈,jdk 是不允許加了 final 的變量為 null 的,會直接編譯錯誤。

3. 加上 volatile 關鍵字,是保證多線程下的內存可見性。即:一個線程修改了 instance 的值,另一個線程馬上就能看到,也就是強制讀主內存,不讀工作內存。

4. 私有化構造方法。同上,防止外部通過 new 創建多個實例。

getSingleton() 詳解:

其實去掉第一個 if 判斷也可以,也就是這樣:

    public static Singleton getSingleton() {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}return instance;}   

1. 加上第一個 if 的好處是:

無鎖判斷,instance 不為空直接返回,為 null 再加同步鎖,提高性能,避免每次獲取都加鎖。

2. 加了鎖之后為什么還要判斷呢?

試想這么一個場景:兩個線程同時來了,都發現 instance 為 null,線程A先獲取了鎖創建了對象,那么線程B獲取鎖后無需創建對象,所以要在判斷一次是否為 null。

3. 靜態內部類(懶加載)

public class Singleton {private Singleton(){}private static class SingletonHolder {private static final Singleton instance = new Singleton();}public static Singleton getSingleton() {return SingletonHolder.instance;}
}

使用靜態內部類的方式實現單例,JVM 會在加載外部類時延遲加載內部類,既能實現懶加載,又能避免多線程問題。

4. 枚舉類

public enum Singleton {INSTANCE;public void test() {}
}

其他所有的實現單例的方式其實是有問題的,那就是可能被反序列化和反射破壞。

枚舉的寫法的優點:

  • 不用考慮懶加載和線程安全的問題,代碼寫法簡潔優雅
  • 線程安全

反編譯任何一個枚舉類會發現,枚舉類里的各個枚舉項是是通過static代碼塊來定義和初始化的,它們會在類被加載時完成初始化,而java類的加載由JVM保證線程安全,所以,創建一個Enum類型的枚舉是線程安全的

  • 防止破壞單例

我們知道,序列化可以將一個單例的實例對象寫到磁盤,然后再反序列化讀回來,從而獲得一個新的實例。即使構造函數是私有的,反序列化時依然可以通過特殊的途徑去創建類的一個新的實例,相當于調用該類的構造函數。

Java對枚舉的序列化作了規定,在序列化時,僅將枚舉對象的name屬性輸出到結果中,在反序列化時,就是通過java.lang.Enum的valueOf來根據名字查找對象,而不是新建一個新的對象。枚舉在序列化和反序列化時,并不會調用構造方法,這就防止了反序列化導致的單例破壞的問題。

對于反射破壞單例的而言,枚舉類有同樣的防御措施,反射在通過newInstance創建對象時,會檢查這個類是否是枚舉類,如果是,會拋出異常java.lang.IllegalArgumentException: Cannot reflectively create enum objects,表示反射創建對象失敗。

5. 模板模式

實現模板方法通常有兩步:

1. 抽象類:定義模板方法和抽象方法,在模板方法里會調用抽象方法。

2. 子類:繼承抽象類,重寫抽象方法。子類運行時調用父類的模板方法,模板方法運行時再去調用子類重寫的抽象方法。

源碼應用(AQS,Reentrantlock)

1. AQS 的模板方法定義

AQS 是基礎的抽象類,提供通用的同步機制。它的 acquire() 和 release() 方法是模板方法。AQS 中的 tryAcquire() 和 tryRelease() 抽象方法,定義了獲取鎖和釋放鎖的具體邏輯。

2. ReentrantLock.lock() 源碼

這里?ReentrantLock.lock() 內部就是調用 AQS 的模板方法?acquire(),1 表示要獲取一個鎖,后續 state 會加1。

這是 AQS 的模板方法,其中 tryAcquire(arg) 方法由子類 ReentrantLock 重寫實現。

AQS 作為一個抽象類,除了被 ReentrantLock 繼承,還被 CountDownLatch、Semaphore繼承。所以說,AQS 提供通用的 模板方法,提高了代碼的復用性。

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

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

相關文章

高清下載油管視頻到本地

下載工具并安裝: yt-dlp官網地址: GitHub - yt-dlp/yt-dlp: A feature-rich command-line audio/video downloader ffmpeg官網地址: Download FFmpeg 注:記住為其添加環境變量 操作命令: 該指令表示以720p碼率下載VIDEO_UR…

Docker掛載數據顯式掛載和隱式掛載的區別

項目使用的Docker file 創建數據卷掛載點,結果發現宿主機目錄中的數據卷路徑下是空的,才知道docker file中創建的數據卷是隱式掛載,并不會在宿主機上留下持久化數據,隨著容器被刪除隱式掛載的數據卷也會跟著被刪除 后面改為在jen…

IOS UITextField 無法隱藏鍵盤問題

設置UITextField 鍵盤按鈕返回鍵為“完成”,即return key 設置done .m代碼設置代理 //設置代理協議 UITextFieldDelegate, self.mobileTextField.delegate self; ///點擊完成鍵隱藏鍵盤 - (BOOL)textFieldShouldReturn:(UITextField *)textField{//取…

【深度學習】Unet的基礎介紹

U-Net是一種用于圖像分割的深度學習模型,特別適合醫學影像和其他需要分割細節的任務。如圖: Unet論文原文 為什么叫U-Net? U-Net的結構像字母“U”,所以得名。它的結構由兩個主要部分組成: 下采樣(編碼…

RT-Thread+STM32L475VET6實現定時器定時功能

文章目錄 前言一、板載資源介紹二、具體步驟1.打開STM32CubeMX進行相關配置1.1 使用外部高速時鐘,并修改時鐘樹1.2 打開定時器(定時器根據自己需求調整)1.3 打開串口1.4 生成工程 2. 配置定時器2.1 打開HWTIMER設備驅動2.2 聲明定時器2.3將stm32l4xx_hal_msp.c中HAL…

Linux /etc/fstab文件詳解:自動掛載配置指南(中英雙語)

Linux /etc/fstab 文件詳解:自動掛載配置指南 在 Linux 系統中,/etc/fstab(File System Table)是一個至關重要的配置文件,它用于定義系統開機時自動掛載的文件系統。如果你想讓磁盤分區、遠程存儲(如 NFS&…

鏈表-基礎訓練(二)鏈表 day14

兩兩交換鏈表中的節點 題目示意: 給定一個鏈表,兩兩交換其中相鄰的節點,并返回交換后的鏈表。 你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。 原先我的思路是圖像上的思路,但是我感覺還是很復雜…

Unity游戲制作中的C#基礎(4)數組聲明和使用

一、數組的聲明 在 C# 中,聲明數組有多種方式,每種方式都有其適用的場景,下面為你逐一詳細介紹: 1. 直接初始化聲明 這種方式直觀且便捷,在聲明數組的同時就為其賦初值,讓數組從誕生之初就擁有了具體的數據…

【Gin-Web】Bluebell社區項目梳理5:投票功能分析與實現

本文目錄 一、投票功能投票流程實現代碼redis投票 一、投票功能 投票流程 首先我們要明確,就是 誰(哪個用戶:userID) 給 哪個帖子(postID) 投了 什么票(贊成票or反對票)。 贊成票…

XUnity.AutoTranslator-deepseek——調用騰訊的DeepSeek V3 API,實現Unity游戲中日文文本的自動翻譯

XUnity.AutoTranslator-deepseek 本項目通過調用騰訊的DeepSeek V3 API,實現Unity游戲中日文文本的自動翻譯。 準備工作 1. 獲取API密鑰 訪問騰訊云API控制臺申請DeepSeek的API密鑰(限時免費)。也可以使用其他平臺提供的DeepSeek API。 …

Python爬蟲-批量爬取股票數據貓各股票代碼

前言 本文是該專欄的第47篇,后面會持續分享python爬蟲干貨知識,記得關注。 本文筆者以股票數據貓為例子,基于Python爬蟲,批量獲取各股票代碼數據。 具體實現思路和詳細邏輯,筆者將在正文結合完整代碼進行詳細介紹。廢話不多說,下面跟著筆者直接往下看正文詳細內容。(附…

《Keras 3 :使用 Vision Transformers 進行物體檢測》:此文為AI自動翻譯

《Keras 3 :使用 Vision Transformers 進行物體檢測》 作者:Karan V. Dave 創建日期:2022 年 3 月 27 日最后修改時間:2023 年 11 月 20 日描述:使用 Vision Transformer 進行對象檢測的簡單 Keras 實現。 (i) 此示例使用 Keras 3 在 Colab 中查看 GitHub 源 介紹 A…

vue-treeselect顯示unknown的問題及解決

問題 解決辦法 去node-modules包里面找到這個組件的源碼,在它dist文件里面找到這個文件,然后搜索unknown,把它刪掉就可以解決了。

深入剖析抽象工廠模式:設計模式中的架構利器

深入剖析抽象工廠模式:設計模式中的架構利器 在軟件開發領域,設計模式是解決常見問題的通用方案,而抽象工廠模式作為創建型設計模式的重要一員,在構建復雜軟件系統時發揮著關鍵作用。它為創建一系列相關或相互依賴的對象提供了一…

python獲取網頁內容 靠譜的做法

獲取網頁內容 response requests.get(url, verifyFalse) 通過這種方式下載網址不太靠譜, 容易出 ssl錯誤 requests.exceptions.SSLError: HTTPSConnectionPool(hostagri.hainan.gov.cn, port443): Max retries exceeded with url: /hnsnyt/xxgk/gfxwj/index_1.html (Caused by…

MFC中CString的Format、與XML中的XML_SETTEXT格式化注意

1、在MFC中導入 "msxml6.dll",并使用其中的XML_SETTEXT函數,此調用在進行格式化的時候,調用的還是CString.Format()函數! 2、用double類型的數據,格式化整形數%d之前,必須將double強轉為int&…

Linux-C-函數棧-SP寄存器

sp(Stack Pointer,棧指針)是計算機體系結構中一個非常重要的寄存器,下面將詳細介紹其作用和原理。 作用 1. 管理棧內存 棧是一種后進先出(LIFO,Last In First Out)的數據結構,在程…

從零開始用react + tailwindcs + express + mongodb實現一個聊天程序(一)

項目包含5個模塊 1.首頁 (聊天主頁) 2.注冊 3.登錄 4.個人資料 5.設置主題 一、配置開發環境 建立項目文件夾 mkdir chat-project cd chat-project mkdir server && mkdir webcd server npm init cd web npm create vitelatest 創建前端項目時我們選擇javascrip…

深入理解 QObject的作用

QObject 作為 Qt 庫中所有對象的基類,其地位無可替代。幾乎 Qt 框架內的每一個類,無論是負責構建用戶界面的 QWidget,還是專注于數據處理與呈現的 QAbstractItemModel,均直接或間接繼承自 QObject。這種繼承體系賦予 Qt 類庫高度的…

22爬蟲:使用Drission Page的兩個案例

案例一:使用DrissionPage抓取BOSS上的招聘信息 使用requests獲取BOSS網站上的內容是非常困難的,但是通過網頁自動化工具DrissionPage或者是Playwright或者是Seleenium是非常容易的,接下來我們就給出使用DrissionPage爬取BOSS網站python招聘的…