singleton设计模式_Java Singleton设计模式
singleton設計模式
它是Java中最簡單的設計模式之一。
如果有人問我哪種設計模式好,那么我會很自豪地說Singleton。
但是,當他們深入詢問單身人士的概念時,我感到很困惑。
真的單身是那么困難嗎?
確實不是,但是它有許多場景需要我們理解(尤其是初學者)。
定義:
在所有情況下,該類僅應允許一個實例,并且我們應提供對該實例的全局訪問點。
定義就像1,2,3和A,B,C,D一樣簡單。
讓我們看看如何實現Singleton類。
我們如何確保對象始終都是一個?
提示:將對象創建邏輯僅放在一個位置,并且不允許用戶每次嘗試都執行此邏輯,而只允許執行一次。
對象創建邏輯->它是什么
我們如何用Java創建對象?
是的,使用builder,我們不應該允許用戶每次嘗試訪問構造器并執行它。
但是我們應該這樣做一次,至少要得到一個對象。
那么,如何確保構造函數僅可訪問和可執行一次?
如何使它->如何防止類外部的方法訪問?
簡單,將make方法作為私有權限,類似地,將構造函數作為私有權限。
如何制作->這有多種實現方式,下面以示例來看。
如果滿足以上兩個條件,則我們班級將始終有一個對象。 該類稱為Singleton,因為它在我們請求的所有時間都產生單個對象。
沒有太多理論,我們現在將開始實施。
創建單例對象的方法有很多:
方法1
- 急于初始化或在使用前初始化
EagerSingletonClass的實例在類啟動時創建。 由于它是靜態的,因此在加載EagerSingletonClass時會對其進行加載和創建。
- Junit測試類為上述類的單例測試。
優勢:
此策略在加載類期間創建對象,因此從多線程方案中可以更快更安全。 我們只需要使實例具有可變性即可處理多線程方案。
壞處 :
這種策略會在類加載本身時創建實例,因此,如果我們不使用它,那么這會浪費整個時間和內存來創建實例。 因此,最好在需要時選擇一種策略來創建實例。
什么時候使用以上策略?
只要我們100%確定在我們的應用程序中肯定使用了該對象。
要么 當物體不重時,我們可以管理速度和內存。
方法2
- 延遲初始化或在需要時初始化
與其在啟動時創建對象,不如在需要時創建對象,這是很好的。 因此,讓我們看看如何做到這一點:
package com.kb.singleton;public class LazySingleton {private static volatile LazySingleton singletonInstance = null;//making constructor as private to prevent access to outsidersprivate LazySingleton() {}public static LazySingleton getInstance(){if(singletonInstance==null){synchronized (LazySingleton.class) {singletonInstance = new LazySingleton();}}return singletonInstance;}}在以上程序中,僅當通過getInstance()方法發出請求時,我們才創建了一個對象。
在此,在首次調用getInstance()的過程中,對象“ singletonInstance”將為null,并在條件變為true時執行if條件塊并創建一個對象。
然后,對getInstance()方法的后續調用將返回相同的object。
但是,如果我們看一下多線程方案,問題就出在以下上下文中:2個線程t1和t2調用getInstance()方法,線程t1執行if(singletonInstance == null)并發現singletonInstance為null,因此它進入同步塊以創建一個目的。
但是在執行對象創建邏輯之前,如果線程t2執行if(singletonInstance == null),那么它還將發現singletonInstance為null,因此它還將嘗試輸入同步塊,但不會像第一個線程t1那樣具有鎖。
因此,線程t2等待線程t1完成同步塊的執行。
因此線程t1執行并創建對象。 現在線程t2也正在等待同步塊時進入同步塊,并再次創建對象。
因此,兩個線程創建了兩個對象。 因此無法實現單例。
解決上述問題的方法是“ 雙重檢查鎖定”。
它說在我們在同步塊內執行對象創建的邏輯之前,請重新檢查同步塊內的實例變量。
這樣一來,我們可以避免多個線程多次創建對象。
怎么樣 ?
線程t1檢查條件if(singletonInstance == null),第一次為真,因此它進入同步塊,然后再次檢查條件if(singletonInstance == null),也為真,因此創建了對象。
現在線程t2進入方法getInstance()并假定它在線程t1執行對象創建邏輯之前已執行if(singletonInstance == null)條件,然后t2也等待進入同步塊。
在線程t1從同步塊中出來之后,線程t2進入了同一塊,但是我們再次在其中有if條件if(singletonInstance == null)但線程t1已經創建了一個對象,它使條件變為false并進一步停止執行并返回相同的實例。
讓我們看看如何在代碼中完成它:
package com.kb.singleton;public class LazySingletonDoubleLockCheck {private static volatile LazySingletonDoubleLockCheck singletonInstance = null;//making constructor as private to prevent access to outsidersprivate LazySingletonDoubleLockCheck() {}public static LazySingletonDoubleLockCheck getInstance(){if(singletonInstance==null){synchronized (LazySingleton.class) {if(singletonInstance ==null){singletonInstance = new LazySingletonDoubleLockCheck();}}}return singletonInstance;}}讓我們做單元測試
package com.kb.singleton;import static org.junit.Assert.*;import org.junit.Test;public class LazySingletonDoubleLockCheckTest {@Testpublic void testSingleton() {LazySingletonDoubleLockCheck instance1 = LazySingletonDoubleLockCheck.getInstance();LazySingletonDoubleLockCheck instance2 = LazySingletonDoubleLockCheck.getInstance();System.out.println("checking singleton objects equality");assertEquals(true, instance1==instance2);//fail("Not yet implemented");}}上面的實現是針對單例模式的最佳建議解決方案,它最適合于單線程,多線程等所有情況。
方法3
- 使用內部類的單例
讓我們看一下下面的使用內部類創建對象的代碼:
package com.kb.singleton;public class SingletonUsingInnerClass {private SingletonUsingInnerClass() {}private static class LazySingleton{private static final SingletonUsingInnerClass SINGLETONINSTANCE = new SingletonUsingInnerClass();}public static SingletonUsingInnerClass getInstance(){return LazySingleton.SINGLETONINSTANCE;}}單元測試代碼
package com.kb.singleton;import static org.junit.Assert.*;import org.junit.Test;public class SingletonUsingInnerClassTest {@Testpublic void testSingleton() {SingletonUsingInnerClass instance1 = SingletonUsingInnerClass.getInstance();SingletonUsingInnerClass instance2 = SingletonUsingInnerClass.getInstance();System.out.println("checking singleton objects equality");assertEquals(true, instance1==instance2);}}以上使用內部類創建對象的方法是創建單例對象的最佳方法之一。
在這里,除非并且直到有人嘗試訪問LazySingleton靜態內部類的靜態引用變量,否則將不會創建該對象。
因此,這還將確保在需要時以及在需要時創建對象。 而且實現起來非常簡單。 從多線程進行也是安全的。
方法4
- 具有序列化和反序列化的Singleton
現在假設我們的應用程序是分布式的,我們序列化我們的單例對象并將其寫入文件。 稍后我們通過取消序列化單例對象來閱讀它。 取消序列化對象始終會創建一個狀態為文件內部可用的新對象。 如果在寫入文件后進行任何狀態更改,然后嘗試取消序列化對象,則將獲得原始對象,而不是新的狀態對象。 因此,我們在此過程中得到了2個對象。
讓我們嘗試通過程序來了解這個問題:
第一件事->使singleton類可序列化以序列化和反序列化此類的對象。
第二件事->將對象寫入文件(序列化)
第三件事->更改對象狀態 第四件事-> de序列化對象
我們的單例課程如下:
package com.kb.singleton;import java.io.Serializable;public class SingletonSerializeAndDesrialize implements Serializable {private int x=100;private static volatile SingletonSerializeAndDesrialize singletonInstance = new SingletonSerializeAndDesrialize();private SingletonSerializeAndDesrialize() {}public static SingletonSerializeAndDesrialize getInstance() {return singletonInstance;}public int getX() {return x;}public void setX(int x) {this.x = x;}}序列化我們的對象,然后對狀態進行一些更改,然后取消序列化。
package com.kb.singleton;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream;public class SerializeAndDeserializeTest {static SingletonSerializeAndDesrialize instanceOne = SingletonSerializeAndDesrialize.getInstance();public static void main(String[] args) {try {// Serialize to a fileObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));out.writeObject(instanceOne);out.close();instanceOne.setX(200);// Serialize to a fileObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));SingletonSerializeAndDesrialize instanceTwo = (SingletonSerializeAndDesrialize) in.readObject();in.close();System.out.println(instanceOne.getX());System.out.println(instanceTwo.getX());} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}輸出:
200
100
它清楚地表明,即使單身,我們也有2個不同的對象。 之所以發生這種情況,是因為反序列化會使用文件中可用狀態創建新實例。
如何克服這個問題? 意味著如何防止在反序列化期間創建新實例?
解決方案非常簡單–在您的singleton類中實現以下方法:
Access_modifier Object readResolve() throws ObjectStreamException{ }例:
Public Object readResolve() throws ObjectStreamException{ return modifiedInstance; }將其應用于上面的單例課程,則完整的單例課程如下:
package com.kb.singleton;import java.io.ObjectStreamException; import java.io.Serializable;public class SingletonSerializeAndDesrialize implements Serializable {private int x=100;private static volatile SingletonSerializeAndDesrialize singletonInstance = new SingletonSerializeAndDesrialize();private SingletonSerializeAndDesrialize() {System.out.println("inside constructor");}public static SingletonSerializeAndDesrialize getInstance() {return singletonInstance;}public int getX() {return x;}public void setX(int x) {this.x = x;}public Object readResolve() throws ObjectStreamException{return singletonInstance;}}現在運行上面的序列化和反序列化類,以檢查兩個實例的輸出。
輸出:
200
200
這是因為,在反序列化過程中,它將調用readResolve()方法,并且我們將返回現有實例,這將阻止創建新實例并確保單例對象。
- 小心序列號
在序列化之后和取消序列化之前,只要類結構發生更改。 然后,在反序列化過程中,它將發現一個不兼容的類,并因此引發異常:java.io.InvalidClassException:SingletonClass; 本地類不兼容:流classdesc serialVersionUID = 5026910492258526905,本地類serialVersionUID = 3597984220566440782
因此,為避免發生此異常,我們必須始終對可序列化的類使用序列號ID。 其語法如下:
private static final long serialVersionUID = 1L;因此,最后通過涵蓋以上所有情況,單例類的最佳解決方案如下,我建議始終使用此解決方案:
package com.kb.singleton;import java.io.Serializable;public class FinalSingleton implements Serializable{private static final long serialVersionUID = 1L;private FinalSingleton() {}private static class LazyLoadFinalSingleton{private static final FinalSingleton SINGLETONINSTANCE = new FinalSingleton();}public static FinalSingleton getInstance(){return LazyLoadFinalSingleton.SINGLETONINSTANCE;}private Object readResolve() {return getInstance();}}翻譯自: https://www.javacodegeeks.com/2014/05/java-singleton-design-pattern.html
singleton設計模式
總結
以上是生活随笔為你收集整理的singleton设计模式_Java Singleton设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使JavaDoc保持最新状态的工具
- 下一篇: Java中的8种原始类型