【Java設計模式】二、單例模式

文章目錄

  • 0、單例模式
  • 1、餓漢式
  • 2、懶漢式
  • 3、雙重檢查
  • 4、靜態內部類
  • 5、枚舉
  • 6、單例模式的破壞:序列化和反序列化
  • 7、單例模式的破壞:反射
  • 8、單例模式的實際應用

設計模式即總結出來的一些最佳實現。GoF(四人組) 書中提到23種設計模式,可分為三大類:

  • 創建型模式:隱藏了創建對象的過程,通過邏輯方法進行創建對象,使用者不用關注對象的創建細節(對那種屬性很多,創建麻煩的對象尤其好用)
  • 結構型模式:主要關注類和對象的組合關系。將類或對象按某種布局組成更大的結構
  • 行為型模式:主要關注對象之間的通信與配合

在這里插入圖片描述

0、單例模式

  • 單例模式即在程序中想要保持一個實例對象,讓某個類只有一個實例
  • 單例類必須自己創建自己唯一的實例,并對外提供
  • 優點:減少了內存開銷

單例模式的實現,有以下幾種思路:

1、餓漢式

  • 類加載就會導致該單實例對象被創建
  • 通過靜態代碼塊或者靜態變量直接初始化

方式一:靜態成員變量的方式

public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton();//私有的構造方法,只能本類調用,不給外界用private HungrySingleton(){}//提供一個獲取實例的方法給外界public static HungrySingleton getInstance(){return hungrySingleton;}
}

方式二:靜態代碼塊

public class HungrySingleton {private static HungrySingleton hungrySingleton = null;//  靜態代碼塊中進行賦值static {hungrySingleton = new HungrySingleton();}//私有的構造方法,只能本類調用,不給外界用private HungrySingleton(){}//提供一個獲取實例的方法給外界public static HungrySingleton getInstance(){return hungrySingleton;}
}

以上兩種方式,對象會隨著類的加載而創建,如果這個對象后來一直沒被用,就有點白占內存了。

2、懶漢式

類加載不會導致該單實例對象被創建,而是首次使用該對象時才會創建


懶漢式方式一:線程不安全
public class LazySingleton  {private static LazySingleton lazySingleton = null;/*** 私有的構造方法,保證出了本類就不能再被調用,以防直接去創建對象*/private LazySingleton() {}/*** 單例對象的獲取*/public static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}}

測試類啟兩個線程獲取對象:

public class Test {public static void main(String[] args) {new Thread(() -> {LazySingleton instance = LazySingleton.getInstance();System.out.println(Thread.currentThread().getName() + "-->" + instance);}, "t1").start();new Thread(() -> {LazySingleton instance = LazySingleton.getInstance();System.out.println(Thread.currentThread().getName()+ "-->" + instance);}, "t2").start();}
}

發現可能出現獲取到兩個不同對象的情況,這是因為線程安全問題:

在這里插入圖片描述

兩個線程A、B,同時執行IF 這一行,被掛起,再被喚醒時繼續往下執行,就會創建出兩個不同的實例對象。那最先想到的應該是synchronized關鍵字解決,但這樣性能低下,因為不管對象是否為null,每次都要等著獲取鎖。

//性能低下,一刀切,不可行
public static synchronized LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}

3、雙重檢查

通過兩個IF判斷,加上同步鎖進行實現。(懶漢式方式二:雙重檢查)

public class DoubleCheckSingleton {private static DoubleCheckSingleton doubleCheckSingleton;/*** 私有的構造方法,保證出了本類就不能再被調用,以防直接去創建對象*/private DoubleCheckSingleton(){}public static DoubleCheckSingleton getInstance(){if(doubleCheckSingleton == null){synchronized (DoubleCheckSingleton.class){if(doubleCheckSingleton == null){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}

如此,再有A、B兩個線程同時執行到第一個IF,只能有一個成功創建對象,另一個獲取到鎖后,第二重判斷會告訴它已經有對象實例了。而亮點則在于,對象初始化完成后,后面來獲取對象的線程不用等著拿鎖,第一個IF就能告訴它已有對象,不用再等鎖了。


以上雙端檢鎖雖然線程安全,但問題是,JVM指令重排序后,可能出現空指針異常,可再加volatile關鍵字(volatile的可見性和有序性,這里用它的有序性)

//...
private static volatile DoubleCheckSingleton doubleCheckSingleton;
//...

4、靜態內部類

在單例類中,通過私有的靜態內部類,創建單例對象(加private修飾詞的,出了本類無法調用和訪問)。這也是懶漢式的第三種方式。

public class StaticInnerClassSingleton {/*** 私有的靜態內部類實例化對象* 給內部類的屬性賦值一個對象*/private static class InnerClass{private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();}/*** 私有的構造方法,保證出了本類就不能再被調用,以防直接去創建對象*/private StaticInnerClassSingleton(){}/*** 對外提供獲取實例的方法*/public static StaticInnerClassSingleton getInstance(){return InnerClass.staticInnerClassSingleton;}
}

外部類StaticInnerClassSingleton被加載時,其對象不一定被初始化,因為內部類沒有被主動使用到。直到調用getInstance方法時,靜態內部類InnerClass被加載,完成實例化。

靜態內部類在被加載時,不會立即實例化,而是在第一次使用時才會被加載并初始化。

JVM在加載外部類的過程中,不會加載靜態內部類,只有內部類的屬性或方法被調用時才會被加載,并初始化其靜態屬性。 這種延遲加載的特性,使得我們可以通過靜態內部類來實現在需要時創建單例對象。這種方式沒有線程安全問題,也沒有性能影響和空間的浪費。

5、枚舉

  • 單例模式的最佳實現方式
  • 枚舉類型是線程安全的,并且只會裝載一次
  • 可有效防止對單例模式的破壞
  • 屬于餓漢式
public enum EnumSingleton {INSTANCE;public static EnumSingleton getInstance(){return INSTANCE;}
}

6、單例模式的破壞:序列化和反序列化

通過流將單例對象,序列化到文件中,然后再反序列化讀取出來。發現通過反序列化方式創建出來的對象內存地址,和原對象不一樣。或者對象序列化到文件后,兩次反序列化得到的對象也不一樣,單例模式被破壞。

public class TestSerializer {public static void main(String[] args) throws Exception {//懶漢式LazySingleton instance = LazySingleton.getInstance();//把對象序列化到文件中ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));oos.writeObject(instance);//從文件中反序列化對象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton"));LazySingleton objInstance = (LazySingleton) ois.readObject();System.out.println(instance);System.out.println(objInstance);System.out.println(instance == objInstance);}
}

在這里插入圖片描述

可以發現單例模式的五種實現方式中,只有枚舉不會被破壞單例模式。如果非要用其他幾種模式,可以加readResolve方法來重寫反序列化邏輯。因為反序列化創建對象時,是通過反射創建的,反射會調用readResolve方法,并將其返回值做為反序列化的結果。 沒有重寫readResolve方法時,會通過反射創建一個新的對象,從而破壞了單例模式。這一點在對象流ObjectInputStream的源碼可看出:

在這里插入圖片描述

public class LazySingleton implements Serializable {private static LazySingleton lazySingleton = null;/*** 私有的構造方法,保證出了本類就不能再被調用,以防直接去創建對象*/private LazySingleton() {}/*** 單例對象的獲取*/public static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}private Object readResolve(){return lazySingleton;}}

在這里插入圖片描述

也可考慮使用@JsonCreator注解。

7、單例模式的破壞:反射

  • 通過字節碼對象,創建構造器對象
  • 通過構造器對象,初始化單例對象
  • 由于單例對象的構造方法是private私有的,調用構造器中的方法,賦予權限,創建單例對象

注意私有修飾詞時,反射會IllegalAccessException

在這里插入圖片描述

處理下private問題,用懶漢模式驗證:

public class TestReflect {public static void main(String[] args) throws Exception{//創建字節碼對象Class<LazySingleton> clazz = LazySingleton.class;//構造器對象Constructor<LazySingleton> constructor = clazz.getDeclaredConstructor();//賦予權限constructor.setAccessible(true);//解決了私有化問題,獲取實例對象LazySingleton o1 = constructor.newInstance();//再次反射LazySingleton o2 = constructor.newInstance();System.out.println(o1);System.out.println(o2);System.out.println(o1 == o2);}
}

在這里插入圖片描述

用枚舉的方式驗證:

public class TestEnumReflect {public static void main(String[] args) throws Exception {Class<EnumSingleton> clazz = EnumSingleton.class;//枚舉下的單例模式,創建構造方法時,需要給兩個參數,藪澤NoSuchMethodException//這兩個參數是源碼中的體現,一個是String,一個是intConstructor<EnumSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);constructor.setAccessible(true);EnumSingleton instanceReflect = constructor.newInstance("test",1234);EnumSingleton instanceSingleton = EnumSingleton.getInstance();System.out.println(instanceReflect);System.out.println(instanceSingleton);System.out.println(instanceReflect == instanceSingleton);}
}

運行報錯:Cannot reflectively create enum objects,即反射創建枚舉的單例對象,是不允許的:

在這里插入圖片描述

在其他單例模式的實現方式里,也可以實現不允許通過反射創建對象,反射靠拿構造方法對象,調整下構造方法(比如雙重檢鎖):

public class DoubleCheckSingleton {private static DoubleCheckSingleton doubleCheckSingleton;/*** 私有的構造方法,保證出了本類就不能再被調用,以防直接去創建對象*/private DoubleCheckSingleton(){if (doubleCheckSingleton != null) {throw new RuntimeException("不允許創建多個對象");}}public static DoubleCheckSingleton getInstance(){if(doubleCheckSingleton == null){synchronized (DoubleCheckSingleton.class){if(doubleCheckSingleton == null){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}

或者:

在這里插入圖片描述

8、單例模式的實際應用

JDK的Runtime類就是餓漢式的單例模式:

在這里插入圖片描述

PS:Runtime類的簡單使用 --> 執行DOS命令并獲取結果

public class RuntimeDemo {public static void main(String[] args) throws IOException {//獲取Runtime類對象Runtime runtime = Runtime.getRuntime();//返回 Java 虛擬機中的內存總量。System.out.println(runtime.totalMemory());//返回 Java 虛擬機試圖使用的最大內存量。System.out.println(runtime.maxMemory());//創建一個新的進程執行指定的字符串命令,返回進程對象Process process = runtime.exec("ipconfig");//獲取命令執行后的結果,通過輸入流獲取InputStream inputStream = process.getInputStream();byte[] arr = new byte[1024 * 1024* 100];//將流輸入到數組,返回讀到的字節個數int b = inputStream.read(arr); //字節數組轉字符串,指定下字符集為GBKSystem.out.println(new String(arr,0 ,b ,"gbk"));}
}

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

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

相關文章

B站畫質補完計劃(2):視頻超分讓像素細膩生動

本期作者 1 前言 為了給用戶提供更清晰的畫質體驗&#xff0c;B站自研的超分辨率算法已經在站內廣泛應用&#xff0c;支持了如《賽馬娘》、《流浪地球2》、《權力的游戲》、英雄聯盟S賽賽事直播等知名番劇、電影電視劇以及重要游戲賽事直播的 4K 視頻流生產。 2 超分算法的應用…

論文閱讀:2020GhostNet華為輕量化網絡

創新&#xff1a;&#xff08;1&#xff09;對卷積進行改進&#xff08;2&#xff09;加殘差連接 1、Ghost Module 1、利用1x1卷積獲得輸入特征的必要特征濃縮。利用1x1卷積對我們輸入進來的特征圖進行跨通道的特征提取&#xff0c;進行通道的壓縮&#xff0c;獲得一個特征濃…

“智農”-高標準農田

高標準農田是指通過土地整治、土壤改良、水利設施、農電配套、機械化作業等措施&#xff0c;提升農田質量和生產能力&#xff0c;達到田塊平整、集中連片、設施完善、節水高效、宜機作業、土壤肥沃、生態友好、抗災能力強、與現代農業生產和經營方式相適應的旱澇保收、穩產高產…

C++設計模式之——享元模式詳解和代碼案例

文章目錄 C中實現享元模式通常涉及以下幾個關鍵部分&#xff1a;一個簡單的C代碼片段示例享元模式的進一步說明C享元模式代碼案例——咖啡店訂單系統享元模式在現實世界的應用場景 C中實現享元模式通常涉及以下幾個關鍵部分&#xff1a; 享元模式&#xff08;Flyweight Patter…

LCR 153. 二叉樹中和為目標值的路徑

解題思路&#xff1a; 回溯&#xff1a;先序遍歷&#xff0b;路徑記錄 class Solution {LinkedList<List<Integer>> res new LinkedList<>();LinkedList<Integer> path new LinkedList<>();public List<List<Integer>> pathTarge…

android 如何動態修改swap

前言 當前項目中發現&#xff0c;產品在長時間使用后&#xff0c;會概率死機&#xff0c;通過log分析&#xff0c;可能和swap 大小太小導致的&#xff0c;需要修改增大swap大小后&#xff0c;壓測驗證。如何查看swap大小 cat /proc/swaps C:\Users\Administrator>adb shel…

元學習(meta-learning)的通俗解釋

目錄 1、什么是元學習 2、元學習還可以做什么 3、元學習是如何訓練的 1、什么是元學習 meta-learning 的一個很經典的英文解釋是 learn to learn&#xff0c;即學會學習。元學習是一個很寬泛的概念&#xff0c;可以有很多實現的方式&#xff0c;下面以目標檢測的例子來解釋…

阿里Replace Anything:一鍵替換萬物,讓圖像編輯更簡單

最近&#xff0c;阿里巴巴智能研究院在AIGC領域可謂動作頻頻&#xff0c;新品發布不斷&#xff0c;在之前的文章已經向大家介紹了關于Animate AnyOne, Outfit Anyone&#xff0c;AnyText, AnyDoor等相關技術&#xff0c;感興趣的小伙伴可以點擊下面鏈接閱讀&#xff5e; AI一鍵…

Laravel - API 項目適用的圖片驗證碼

1. 安裝 gregwar/captcha 圖片驗證碼接口的流程是&#xff1a; 生成圖片驗證碼 生成隨機的 key&#xff0c;將驗證碼文本存入緩存。 返回隨機的 key&#xff0c;以及驗證碼圖片 # 不限于 laravel 普通 php 項目也可以使用額 $ composer require gregwar/captcha2. 開發接口 …

神經網絡算法詳解以及應用場景

神經網絡算法是一類基于神經網絡思想的機器學習算法。神經網絡是一種模擬人腦神經系統的計算模型&#xff0c;由大量的人工神經元組成&#xff0c;這些神經元通過可調的連接權值相互連接&#xff0c;形成復雜的網絡結構。神經網絡具有大規模并行處理、分布式信息存儲、良好的自…

小塔RFID技術幫您解決“倉儲管理危機”!

商品積壓對一個企業帶來的影響是久遠的&#xff0c;倉儲管理流轉失衡&#xff1a;庫存數據不準確、繁瑣人工管理費時費力、商品爆倉及庫存短缺等造成“倉儲管理危機”&#xff0c;讓企業自身陷入困境。 優化倉儲管理&#xff0c;小塔RFID倉儲管理方案輕松解決。利用RFID&#x…

java數據結構與算法刷題-----LeetCode538. 把二叉搜索樹轉換為累加樹

java數據結構與算法刷題目錄&#xff08;劍指Offer、LeetCode、ACM&#xff09;-----主目錄-----持續更新(進不去說明我沒寫完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目錄 解題思路 BST二叉搜索樹&#xff0c;中序遍歷結果為一個升序序列…

【C語言】三子棋

前言&#xff1a; 三子棋是一種民間傳統游戲&#xff0c;又叫九宮棋、圈圈叉叉棋、一條龍、井字棋等。游戲規則是雙方對戰&#xff0c;雙方依次在9宮格棋盤上擺放棋子&#xff0c;率先將自己的三個棋子走成一條線就視為勝利。但因棋盤太小&#xff0c;三子棋在很多時候會出現和…

心得 醒悟

學習是一個學習&#xff0c;遺忘&#xff0c;回憶。。。的一個不間斷個過程&#xff0c;學習python很心急&#xff0c;沒有怎么實操&#xff0c;開著2倍速看視頻&#xff0c;導致看過后很多對列表&#xff0c;字典的命令等等就忘記了&#xff0c;沒有真的學會&#xff0c;導致現…

Unity(第十四部)光照

原始的有默認燈光、除了默認的你還可以創建 1、定向光源&#xff08;類似太陽、從無限遠的地方射向地面的光&#xff0c;光源位置并不影響照射角度等&#xff0c;不同方向的旋轉影響角度和明亮&#xff09; 1. 顏色&#xff1a;調整光的顏色2. 模式&#xff1a;混合是實時加烘…

FCU2601嵌入式控制單元獲得開普「電磁兼容檢驗證書」

近日&#xff0c;飛凌嵌入式專為鋰電池儲能行業設計的FCU2601嵌入式控制單元獲得了開普電磁兼容檢驗證書&#xff0c;此次性能檢驗項目包括高頻干擾檢驗、靜電放電干擾檢驗、輻射電磁場干擾檢驗、快速瞬變脈沖群干擾檢驗、浪涌干擾檢驗、工頻磁場干擾檢驗、阻尼振蕩磁場干擾檢驗…

基于docker實現MySQL主從復制(全網最詳細!!!)

一、 通過docker鏡像搭建MySQL主從 主服務器&#xff1a;容器名zi-mysql-master&#xff0c;端口3306 從服務器&#xff1a;容器名zi-mysql-slave1&#xff0c;端口3307 從服務器&#xff1a;容器名zi-mysql-slave2&#xff0c;端口3308 二、 關閉防火墻&#xff0c;啟動docker…

免費百度快速收錄軟件

在網站SEO的過程中&#xff0c;不斷更新網站內容是提升排名和吸引流量的關鍵之一。而對于大多數網站管理員來說&#xff0c;頻繁手動更新文章并進行SEO優化可能會是一項繁瑣且耗時的任務。針對這一問題&#xff0c;百度自動更新文章SEO工具應運而生&#xff0c;它能夠幫助網站管…

基于R語言APSIM模型進階應用與參數優化、批量模擬教程

原文&#xff1a;基于R語言APSIM模型進階應用與參數優化、批量模擬教程 前沿 隨著數字農業和智慧農業的發展&#xff0c;基于過程的農業生產系統模型在模擬作物對氣候變化的響應與適應、農田管理優化、作物品種和株型篩選、農田固碳和溫室氣體排放等領域扮演著越來越重要的作…

全域營銷、全員營銷與霸詞、霸屏、霸網:揭秘數字營銷的五大核心策略

全域營銷、全員營銷與霸詞、霸屏、霸網&#xff1a;揭秘數字營銷的五大核心策略 隨著科技的飛速發展和互聯網的深度滲透&#xff0c;數字營銷已成為企業品牌推廣和市場拓展的必備手段。全域營銷、全員營銷、霸詞、霸屏和霸網這五大策略&#xff0c;更是引領著數字營銷的新潮流…