<JavaEE> 經典設計模式之 -- 單例模式(“餓漢模式”和“懶漢模式”實現單例模式)

目錄

一、單例模式概述

二、“餓漢模式”實現單例模式

三、“懶漢模式”實現單例模式

3.1 單線程下的“懶漢模式”

3.2 多線程下的“懶漢模式”


一、單例模式概述

1)什么是單例模式?

單例模式是一種設計模式。

單例模式可以保證某個類在程序中只存在唯一實例,即不允許創建多份實例。

使用單例模式,上述要求就得到了檢查和校驗。

2)單例模式的實現形式

單例模式可以通過很多種方法實現,“餓漢模式”和“懶漢模式”是其中最基礎的兩種,本文只介紹這兩種實現。

二、“餓漢模式”實現單例模式

通過代碼演示“餓漢模式”實現的單例模式:

class Singleton{//新建一個唯一實例;private static Singleton instance = new Singleton();//方法返回唯一實例;public static Singleton getInstance() {return instance;}//將構造方法私有化;private Singleton() { }
}
1)上述代碼做了什么?

創建了一個被?static 修飾的實例,這個實例成為了類屬性。類對象只會有一個,這個類屬性也只會有一個。

私有化構造方法,外部無法 new 新的實例,只能通過 get 方法獲取唯一的那一個 instance。
2)為什么叫做“餓漢模式”?

上述代碼中,實例是類屬性。類屬性在類加載的時候就創建了,創建時機早,十分“迫切”,因此稱為“餓漢模式”。

代碼證明“餓漢模式”返回的實例是唯一的:

public class Singleton_Demo0 {public static void main(String[] args) {//想直接new對象,就會報錯;//Singleton instance = new Singleton();//兩次調用getInstance()方法并分別賦值;Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();//對比兩個變量,發現是同一實例;if(instance1 == instance2){System.out.println("兩個對象是同一個對象");}}
}//運行結果:
兩個對象是同一個實例
3)“餓漢模式”的單例模式在多線程下是線程安全的嗎?

上述代碼中,get 方法返回的是已經創建好的實例,這個操作本質上只是一個“讀操作”,多個線程讀取同一個變量并不會造成線程不安全。

因此“餓漢模式”的單例模式在多線程下是線程安全的。

三、“懶漢模式”實現單例模式

3.1 單線程下的“懶漢模式”

通過代碼演示懶漢模式現的單例模式:

class Singleton{//聲明一個變量作為類屬性;private static Singleton instance = null;//判斷變量是否為null,是則創建實例后返回,否則返回;public static Singleton getInstance() {if(instance == null){instance = new Singleton();}return instance;}//將構造方法私有化;private Singleton() { }
}
1)上述代碼做了什么?

聲明了一個類屬性。類對象只會有一個,這個類屬性也只會有一個。

私有化構造方法,外部無法 new 新的實例,只能通過 get 方法獲取唯一的那一個 instance。
get 方法中根據變量是否為 null 判斷是否應該創建實例。
2)為什么叫做“懶漢模式”?

上述代碼中,實例是在程序員第一次調用 get 方法后才創建的,創建時機較晚,或者根本不用創建,因此稱為“懶漢模式”。

3.2 多線程下的“懶漢模式”

1)單線程下的“懶漢模式”在多線程下是線程安全的嗎?

答案是否定的,單線程下的“懶漢模式”在多線程下是線程不安全的,我們可以從以下兩個方面分析:

“原子性”:

上述代碼中判斷變量是否為空的代碼 —— if(instance == null),和實例化代碼 ——?

instance = new Singleton(),并非是“原子”的。在多線程環境下,這就可能導致線程不安全。

可以使用 synchronized 關鍵字,將這兩句代碼加鎖,解決這個問題。

內存可見性和指令重排序:

因為 instance 是一個被?static 修飾的共享數據,而且編譯器內部可能對實例化的代碼 ——?new Singleton(),進行了編譯器優化。

這就無法保證內存的可見性和指令的順序執行,因此在多線程環境下可能導致線程不安全。

可以使用 volatile 關鍵字,對共享數據?instance 進行修飾,解決這個問題。

使用以上兩個關鍵字的原因和方式,詳細請參考以下博客:

閱讀指針 -> 《synchronized 關鍵字 和?volatile 關鍵字》<JavaEE> synchronized關鍵字和鎖機制 -- 鎖的特點、鎖的使用、鎖競爭和死鎖、死鎖的解決方法-CSDN博客文章瀏覽閱讀70次。介紹了 synchronized 關鍵字 和 鎖機制,其中重點介紹了鎖的特點、使用方法和死鎖的相關內容。https://blog.csdn.net/zzy734437202/article/details/134742168<JavaEE> volatile關鍵字 -- 保證內存可見性、禁止指令重排序-CSDN博客文章瀏覽閱讀59次。簡單介紹什么是內存可見性和指令重排序。volatile關鍵字可以將這兩種編譯器優化強制關閉。https://blog.csdn.net/zzy734437202/article/details/134757070

2)“懶漢模式”在多線程下應該怎么編寫?

根據上述分析,根據單線程模式下的“懶漢模式”進行改進。

方法如下:

增加?volatile 關鍵字對共享數據進行修飾。

為判斷是否為 null 和 實例化的代碼加鎖,使這兩句代碼稱為“原子”。

增加?volatile 關鍵字對共享數據進行修飾:

private volatile static Singleton instance = null;

為判斷是否為 null 和 實例化的代碼加鎖,使這兩句代碼稱為“原子”:

    public static Singleton getInstance() {synchronized (locker){if(instance == null){instance = new Singleton();}}return instance;}
3)“雙重校驗鎖”

我們再仔細分析一下上述的 get 方法。

假設程序需要多次調用這個 get 方法,那么每一次進入都會進行加鎖,加鎖是會增加系統開銷的。

那么是否真的有必要每次都加鎖呢?

當 get 方法被第一次調用,實例就會被創建,那么后續再調用這個 get 方法時,返回實例就好了,加鎖部分的代碼塊,完全可以不用執行。

在加鎖的代碼塊之外,再增加一個if(instance == null)進行判斷,那么實例在被創建之后,也就不會再進入加鎖的代碼塊中了。

我們成功利用“雙重校驗鎖”,優化了程序。

代碼演示“雙重校驗鎖”優化后的 get 方法:

    public static Singleton getInstance() {//這個if用于判斷是否需要加鎖;if(instance == null){synchronized (locker){//這個if用于判斷是否需要新建實例;if(instance == null){instance = new Singleton();}}}return instance;}

經過以上的完善和優化,我們終于可以寫出在多線程下保證線程安全的“懶漢模式”單例模式了:

class Singleton{//聲明一個變量作為類屬性;private volatile static Singleton instance = null;private static final Object locker = new Object();//判斷變量是否為null,是則創建實例后返回,否則返回;public static Singleton getInstance() {//這個if用于判斷是否需要加鎖;if(instance == null){synchronized (locker){//這個if用于判斷是否需要新建實例;if(instance == null){instance = new Singleton();}}}return instance;}//將構造方法私有化;private Singleton() { }
}

閱讀指針 -> 《經典設計模式之 -- 使用阻塞隊列實現“生產者-消費者模型”》

<JavaEE> 經典設計模式之 -- 使用阻塞隊列實現“生產者-消費者模型”-CSDN博客自己實現了的阻塞隊列,介紹了經典的設計模式“生產者-消費者模型”。https://blog.csdn.net/zzy734437202/article/details/134807241

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

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

相關文章

【Java數據結構 -- 順序表】

List和ArrayList與順序表 一. List1.1 List介紹2.1 常見接口介紹3.1 List的使用 二. ArrayList與順序表1.線性表2.順序表2.1 接口的實現2.2 順序表的創建2.3 順序表的打印2.4 順序表的插入2.5 順序表的按索引位置插入數據2.6 判斷順序表是否包含某個數2.7 返回順序表某個數的索…

Java 22種設計模式詳解

22種設計模式詳解 創建型模式單例模式工廠方法模式抽象工廠模式建造者模式原型模式 結構型模式適配器模式橋接模式組合模式裝飾器模式代理模式外觀模式享元模式享元模式原理:享元模式角色:示例代碼: 行為型模式模板方法模式原理角色示例代碼命…

UEFI 學習筆記

引言 相比于Windows/MacOS/Linux等主流的操作系統,大部分人對于固件BIOS(Basic Input & Output System)并不熟悉,它誕生于1981年出產的第一代個人計算機IBM PC,在那時工程師將硬件檢測代碼、最基本的外圍設備I/O處理程序和操作系統引導程序代碼寫入僅有32KB大小的PRO…

(1)(1.4) ESP32 wifi telemetry

文章目錄 前言 1 用于ESP32的DroneBridge 2 推薦的硬件 3 下載和燒錄固件 4 為ESP32配置DroneBridge 前言 ESP32 是現成的 Wi-Fi 模塊,具有完整的 TCP/IP 協議棧和微控制器功能。它們提供專用的 UART、SPI 和 I2C 接口。它們可與任何 ArduPilot 自動駕駛控制器…

2023-12學習筆記

1.NonNull要手動寫無參構造器 這是一個我今天研究了很久的問題,開始不知道原因是在這里,還在那想是不是Data覆蓋了無參構造,結果當然不是。先說下解決歷程 1.問題起因 通過RequestBody接收前端報文的時候報錯,大致是說我構造方…

python中tkinter實現GUI程序:三個實例

python中tkinter實現GUI程序 寫在最前面Python中使用Tkinter實現GUI程序的基本元素Tkinter簡介基本元素1. 根窗口(Root Window)2. 小部件(Widgets)3. 布局管理4. 事件處理 1.用 tkinter實現一個簡單的 GUI程序,單擊“click”按鈕&…

項目方看過來,詳解Moonbeam Grants申請技巧

Grants加速計劃是Moonbeam基金會發起的生態資助計劃,旨在支持生態系統中的不同項目、社區和個人,幫助他們順利進入Moonbeam發展,依靠早期的生態扶持迅速成長。 每一份Grant計劃旨在融合社區參與、包容性和透明度等特性,加速Moonb…

Java項目-瑞吉外賣Day3

填充公共字段: 目的:由于某些屬性,例如createdTime這些需要填充的字段會在多個地方出現,所以考慮使用公共字段自動填充的辦法減少重復代碼。 在對應屬性上加入TableField注解。通過fill字段表明策略,是插入/更新的時候…

Android studio之ConstraintLayout使用

文章目錄 優勢使用ConstraintLayout的步驟使用多種方式定義控件的位置 優勢 ConstraintLayout是一個相對布局,是在Android 2.3版本中引入的。它可以在不同的屏幕大小和分辨率中提供一致的布局,并且是支持復雜布局的最佳選擇之一。ConstraintLayout相對于…

ROS2+ROS_DOMAN_ID

The ROS_DOMAIN_ID? Table of Contents Overview Choosing a domain ID (short version) Choosing a domain ID (long version) Platform-specific constraints Participant constraints Domain ID to UDP Port Calculator Overview? As explained elsewhere, the de…

Dockerfile模板和Docker Compose模板

記錄一下Dockerfile模板和Docker Compose模板, 基礎的系統加JDK環境來構建一個Java應用,其Dockerfile內容如下: # 基礎鏡像 FROM openjdk:11.0-jre-buster # 設定時區 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/local…

如何搭建廢品上門回收小程序

如今,隨著環境保護意識的增強,廢品的回收和再利用變得越來越重要。為了方便人們進行廢品回收,搭建一個廢品上門回收的小程序成為了一個不錯的選擇。本文將介紹如何從零開始搭建一個廢品上門回收小程序。 …

vue寶典之項目結構介紹

文章目錄 🍁前言🍁Vue.js基本概念🍁Vue.js核心特性🍁Vue.js應用場景🍁Vue項目結構🍁Vue開發流程 目前在學習vue項目,之前只是學習vue中基本語法,當接觸項目時發現vue項目結構之間配置…

【Go自學版】02-goroutine

利用時間片分割進程,致使宏觀上A,B,C同時執行(并發) CPU利用率包含了執行和切換,進程/線程的數量越多,切換成本也會增大 最大并行數:GOMAXPROCS work stealing: 偷其他隊列的G hand off: 當前G1阻塞&#…

css 修改滾動條樣式,解決Windows瀏覽器中滾動條不美觀問題

Windows環境中的瀏覽器中滾動條默認是直接顯示了,不管光標是否進入該區域,這樣就很不美觀,如下圖: 之前樣式為 .well {display: block;background-color: #f2f2f2;border: 1px solid #ccc;margin: 5px;width: calc(100% - 12px);h…

mycat部署和配置讀寫分離(二)

說明: MyCAT 是使用 JAVA 語言進行編寫開發,使用前需要先安裝 JAVA 運行環境(JRE),由于 MyCAT 中使用了 JDK7 中的一些特性,所以要求必須在 JDK7 以上的版本上運行。 1. jdk1.8安裝 詳見jdk環境安裝 2. Mysql安裝 詳見mysql8.0.11源碼安裝…

websoket 的使用

WebSocket是HTML5的API之一,允許瀏覽器和服務器之間進行雙向通信。Vue.js可以輕松地與WebSocket API集成,使用原生WebSocket API或其他WebSocket庫(如socket.io)都是可行的。 下面是一個使用Vue.js實現WebSocket的簡單示例&#…

【lesson11】數據類型之string類型

文章目錄 數據類型分類string類型set類型測試 enum類型測試 string類型的內容查找找所有女生(enum中)找愛好有游泳的人(set中)找到愛好中有足球和籃球的人 數據類型分類 string類型 set類型 說明: set:集…

SL9008 3.6-60V輸入 LED降壓恒流芯片 內置MOS管 帶PWM調光

SL9008是一款內置MOS管、具有PWM調光功能的LED降壓恒流芯片,適用于3.6-60V的輸入電壓范圍。它采用了先進的電路設計,確保了高效率和長壽命,同時具有寬電壓輸入范圍和優異的負載調整率。 SL9008的主要特點包括: 1. 寬輸入電壓范圍&…

C語言中常用的庫函數和頭文件

下面是C語言中常用的一部分庫函數和頭文件,不同編譯器或操作系統可能會有所差異。 1. 字符串相關函數 (string.h): - strlen:獲取字符串長度。 - strcpy、strncpy:復制字符串。 - strcat、strncat:連接字符串…