日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java设计模式】——单例模式

發布時間:2024/3/13 java 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java设计模式】——单例模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

單例模式

  • 🍋單例模式
    • 🍉1.單例模式的結構
    • 🍉2.單例模式的實現
      • 🍊2.1餓漢式
      • 🍊2.2懶漢式
    • 🍉3.單例模式的破壞
      • 🍊3.1序列化和反序列化
      • 🍊3.2反射

🍋單例模式

單例模式是Java中最簡單的設計模式之一,屬于創建型模式,它提供一種創建對象的最佳方式。

單例模式顧名思義就是單一的實例,涉及到一個單一的類,該類負責創建自己的對象,同時確保只有一個對象被創建,并且提供一種可以訪問這個對象的方式,可以直接訪問,不需要實例化該類的對象。

單例模式的特點:
1.單例類只能有一個實例
2.這個實例必須由單例類自己創建
3.單例類需要提供給外界訪問這個實例

單例模式的作用:
單例模式主要為了保證在Java應用程序中,一個類只有一個實例存在。

🍉1.單例模式的結構

單例模式主要有以下角色:

  • 單例類

只能創建一個實例的類

  • 訪問類

測試類,就是使用單例類的類

🍉2.單例模式的實現

🍊2.1餓漢式

餓漢式:類加載時創建該單實例類對象

🌰1.餓漢式-方式1 靜態成員變量

創建 餓漢式靜態成員變量 單例類

public class Demo1 {/***私有構造方法 讓外界不能創建該類對象*/private Demo1(){}/*** 在類中創建該本類對象 static是由于外界獲取該類對象的方法getInstance()是 static* 這個對象instance就是靜態成員變量*/private static Demo1 instance = new Demo1();/*** 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創建對象,直接通過類訪問*/public static Demo1 getInstance(){return instance;} }

創建 餓漢式靜態成員變量 測試類(訪問類)

public class Test1 {public static void main(String[] args) {//創建demo1類的對象 這個時候就無法通過new創建了,因為demo1的構造方法是私有的Demo1 instance = Demo1.getInstance();Demo1 instance1 = Demo1.getInstance();//判斷兩個對象是否是同一個System.out.println(instance == instance1);} }

輸出true 表明是同一個對象,指向同一塊內存地址,這樣我們就保證了Demo1單例類只有一個對象被創建

🌰2.餓漢式-方式2 靜態代碼塊

創建 餓漢式靜態代碼塊 單例類

public class Demo2 {//餓漢式單例類 靜態代碼塊/***私有構造方法 讓外界不能創建該類對象*/private Demo2(){}/*** 聲明一個靜態的成員變量instance但是不賦值(不創建對象)* 沒有為instance賦值,默認為null*/private static Demo2 instance;/*** 在靜態代碼快中為instance賦值(創建對象)*/static {instance = new Demo2();}/*** 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創建對象,直接通過類訪問*/public static Demo2 getInstance(){return instance;} }

創建 餓漢式靜態代碼塊 測試類

public class Test2 {public static void main(String[] args) {Demo2 instance = Demo2.getInstance();Demo2 instance1 = Demo2.getInstance();System.out.println(instance == instance1);} }

輸出true 表明是同一個對象,指向同一塊內存地址,這樣我們就保證了Demo2單例類只有一個對象被創建

🌰3.餓漢式-方式3(枚舉方式)

枚舉類實現單例模式是十分推薦的一種單例實現模式,由于枚舉類型是線程安全的,并且只會加載一次,這是十分符合單例模式的特點的,枚舉的寫法很簡單,而且枚舉方式是所有單例實現中唯一一個不會被破環的單例實現模式

單例類

//枚舉方式創建單例 public enum Singleton {INSTANCE; }

測試類

public class Test1 {public static void main(String[] args) {Singleton instance = Singleton.INSTANCE;Singleton instance1 = Singleton.INSTANCE;System.out.println(instance == instance1);//輸出 true} }

注意:

? 由于枚舉方式是餓漢式,因此根據餓漢式的特點,枚舉方式也會造成內存浪費,但是在不考慮內存問題下,枚舉方式是首選,畢竟實現最簡單了

🍊2.2懶漢式

懶漢式:類加載時不會創建該單實例對象,首次使用該對象時才會創建

🌰1.懶漢式-方式1 (線程不安全)

public class Demo3 {/***私有構造方法 讓外界不能創建該類對象*/private Demo3(){}/*** 在類中創建該本類對象 static是由于外界獲取該類對象的方法getInstance()是 static* 沒有進行賦值(創建對象)*/private static Demo3 instance;/*** 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創建對象,直接通過類訪問*/public static Demo3 getInstance(){//在首次使用該對象時創建,因此instance賦值也就是對象創建 就是在外界獲取該單例類的方法getInstance()中創建instance = new Demo3();return instance;}} public class Test3 {public static void main(String[] args) {Demo3 instance = Demo3.getInstance();Demo3 instance1 = Demo3.getInstance();//判斷兩個對象是否是同一個System.out.println(instance == instance1);} }

輸出結果為false,表明我們創建懶漢式單例失敗了。是因為我們在調用getInstance()時每次調用都會new一個實例對象,那么也就必然不可能相等了。

// 如果instance為null,表明還沒有創建該類的對象,那么就進行創建if(instance == null){instance = new Demo3();}//如果instance不為null,表明已經創建過該類的對象,根據單例類只能創建一個對象的特點,因此 //我們直接返回instancereturn instance;}

注意:

我們在測試是只是單線程,但是在實際應用中必須要考慮到多線程的問題。我們假設一種情況,線程1進入if判斷然后還沒來得及創建instance,這個時候線程1失去了cpu的執行權變為阻塞狀態,線程2獲取cpu執行權,然后進行if判斷此時instance還是null,因此線程2為instance賦值創建了該單例對象,那么等到線程1再次獲取cpu執行權,也進行了instance賦值創建了該單例對象,單例模式被破壞。

🌰2.懶漢式-方式2 (線程安全)

我們可以通過加synchronized同步鎖的方式保證單例模式在多線程下依舊有效

public static synchronized Demo3 getInstance(){//在首次使用該對象時創建,因此instance賦值也就是對象創建 就是在外界獲取該單例類的方法getInstance()中創建// 如果instance為null,表明還沒有創建該類的對象,那么就進行創建if(instance == null){instance = new Demo3();}//如果instance不為null,表明已經創建過該類的對象,根據單例類只能創建一個對象的特點,因此我們直接返回instancereturn instance;}

注意:

雖然保證了線程安全問題,但是在getInstance()方法上添加了synchronized關鍵字,導致該方法執行效率很低(這是加鎖的一個常見問題)。其實我們可以很容易發現,我們只是在判斷instance時需要解決多線程的安全問題,而沒必要在getInstance()上加鎖

🌰3.懶漢式-方式3(雙重檢查鎖)

對于getInstance()方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,沒必要讓每個線程必須持有鎖才能調用該方法,我們可以調整加鎖的時機。

public class Demo4 {/***私有構造方法 讓外界不能創建該類對象*/private Demo4(){}/**** 沒有進行賦值(創建對象) 只是聲明了一個該類的變量*/private static Demo4 instance;/*** 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創建對象,直接通過類訪問*/public static Demo4 getInstance(){// (第一次判斷)如果instance為null,表明還沒有創建該類的對象,那么就進行創建if(instance == null){synchronized (Demo4.class){//第二次判斷 如果instance不為nullif(instance == null){instance = new Demo4();}}}//如果instance不為null,表明已經創建過該單例類的對象,不需要搶占鎖,直接返回return instance;}}

雙重檢查鎖模式完美的解決了單例、性能、線程安全問題,但是只是這樣還是有問題的…

JVM在創建對象時會進行優化指令重排,在多線程下可能會發生空指針異常的問題,可以使用volatile關鍵字,volatile可以保證可見性和有序性。

private static volatile Demo4 instance;

如果發生指令重排 2 和 3 的步驟顛倒,那么instance會指向一塊虛無的內存(也有可能是有數據的一塊內存)

完整代碼

public class Demo4 {/***私有構造方法 讓外界不能創建該類對象*/private Demo4(){}/*** volatile可以保證有序性* 沒有進行賦值(創建對象) 只是聲明了一個該類的變量*/private static volatile Demo4 instance;/*** 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創建對象,直接通過類訪問*/public static Demo4 getInstance(){// (第一次判斷)如果instance為null,表明還沒有創建該類的對象,那么就進行創建if(instance == null){synchronized (Demo4.class){//第二次判斷 如果instance不為nullif(instance == null){instance = new Demo4();}}}//如果instance不為null,表明已經創建過該單例類的對象,不需要搶占鎖,直接返回return instance;} }

🌰4.懶漢式-4 (靜態內部類)

靜態內部類單例模式中實例由內部類創建,由于JVM在加載外部類的過程中,是不會加載靜態內部類的,只有內部類的屬性/方法被調用時才會被加載,并初始化其靜態屬性。靜態屬性由于被final修飾,保證只被實例化一次,并且嚴格保證實例化順序。

創建單例類

public class Singleton {private Singleton(){}/***定義一個靜態內部類*/private static class SingletonHolder{//在靜態內部類中創建外部類的對象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;} }

創建測試類

public class Test4 {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();//判斷兩個對象是否是同一個System.out.println(instance == instance1);} }

注意:

? 第一次加載Singleton類時不會去初始化INSTANCE,只有在調用getInstance()方法時,JVM加載SingletonHolder并初始化INSTANCE,這樣可以保證線程安全,并且Singleton類的唯一性

? 靜態內部類單例模式是一種開源項目比較常用的單例模式,在沒有任何加鎖的情況下保證多線程的安全,并且沒有任何性能和空間上的浪費

🍉3.單例模式的破壞

單例模式最重要的一個特點就是只能創建一個實例對象,那么如果能使單例類能創建多個就破壞了單例模式(除了枚舉方式)破壞單例模式的方式有兩種:

🍊3.1序列化和反序列化

從以上創建單例模式的方式中任選一種(除枚舉方式),例如靜態內部類方式

//記得要實現Serializable序列化接口 public class Singleton implements Serializable {private Singleton(){}/***定義一個靜態內部類*/private static class SingletonHolder{//在靜態內部類中創建外部類的對象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;} }

測試類

public class Test1 {public static void main(String[] args) throws IOException {writeObjectToFile();}/*** 向文件中寫數據(對象)* @throws IOException*/public static void writeObjectToFile() throws IOException {//1.獲取singleton對象Singleton instance = Singleton.getInstance();//2.創建對象輸出流對象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\1.txt"));//3.寫對象oos.writeObject(instance);//4.釋放資源oos.close();} }

在d盤根目錄下出現一個文件1.txt由于數據是序列化后的 咱也看不懂

然后我們從這個文件中讀取instance對象

public static void main(String[] args) throws Exception {// writeObjectToFile();readObjectFromFile();readObjectFromFile();}/*** 從文件中讀數據(對象)* @throws Exception*/public static void readObjectFromFile() throws Exception {//1.創建對象輸入流對象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\1.txt"));//2.讀對象Singleton instance = (Singleton) ois.readObject();System.out.println(instance);//3.釋放資源ois.close();}

輸出結果不相同,結論為:序列化破壞了單例模式,兩次讀的對象不一樣了

com.xue.demo01.Singleton@2328c243
com.xue.demo01.Singleton@bebdb06

解決方案

在singleton中添加readResolve方法

/*** 當進行反序列化時,會自動調用該方法,將該方法的返回值直接返回* @return*/public Object readResolve(){return SingletonHolder.INSTANCE;}

重新進行寫和讀,發現兩次讀的結果是相同的,解決了序列化破壞單例模式的問題

為什么在singleton單例類中添加readResolve方法就可以解決序列化破壞單例的問題呢,我們在ObjectInputStream源碼中在readOrdinaryObject方法中

private Object readOrdinaryObject(boolean unshared)throws IOException{ //代碼段 Object obj;try {//isInstantiable如果一個實現序列化的類在運行時被實例化就返回true//desc.newInstance()會通過反射調用無參構造創建一個新的對象obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}//代碼段if (obj != null &&handles.lookupException(passHandle) == null &&//hasReadResolveMethod 如果實現序列化接口的類中定義了readResolve方法就返回truedesc.hasReadResolveMethod()){//通過反射的方式調用被反序列化類的readResolve方法Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}//代碼段}

🍊3.2反射

從以上創建單例模式的方式中任選一種(除枚舉方式),例如靜態內部類方式

測試類

public class Test1 {public static void main(String[] args) throws Exception {//1.獲取Singleton的字節碼對象Class<Singleton> singletonClass = Singleton.class;//2.獲取無參構造方法對象Constructor cons = singletonClass.getDeclaredConstructor();//3.取消訪問檢查cons.setAccessible(true);//4.反射創建對象Singleton instance1 = (Singleton) cons.newInstance();Singleton instance2 = (Singleton) cons.newInstance();System.out.println(instance1 == instance2);//輸出false 說明反射破壞了單例模式}}

解決方案:

public class Singleton {//static是為了都能訪問private static boolean flag = false;private Singleton() {//加上同步鎖,防止多線程并發問題synchronized (Singleton.class) {//判斷flag是否為true,如果為true說明不是第一次創建,拋異常if (flag) {throw new RuntimeException("不能創建多個對象");}//flag的值置為trueflag = true;}}/***定義一個靜態內部類*/private static class SingletonHolder{//在靜態內部類中創建外部類的對象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;} }

這樣就不能通過之前的反射方式破壞單例模式了,但是如果通過反射修改flag的值也是可以破壞單例模式的,但是這樣可以防止意外反射破壞單例模式,如果刻意破壞是很難防范的,畢竟反射太強了🤣🤣🤣

總結

以上是生活随笔為你收集整理的【Java设计模式】——单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。