Java Singleton设计模式
它是Java中最簡單的設(shè)計模式之一。
如果有人問我哪種設(shè)計模式好,那么我會很自豪地說Singleton。
但是,當(dāng)他們深入詢問單身人士的概念時,我感到很困惑。
真的單身是那么困難嗎?
確實(shí)不是,但是它有許多我們需要了解的場景(尤其是初學(xué)者)。
定義:
在所有情況下,該類只應(yīng)允許一個實(shí)例,并且我們應(yīng)提供對該實(shí)例的全局訪問點(diǎn)。
定義就像1,2,3和A,B,C,D一樣簡單。
讓我們看看如何實(shí)現(xiàn)Singleton類。
我們?nèi)绾未_保對象始終都是一個?
提示:將對象創(chuàng)建邏輯僅放在一個位置,并且不允許用戶每次嘗試執(zhí)行該邏輯時都只能執(zhí)行一次。
對象創(chuàng)建邏輯->它是什么
我們?nèi)绾斡肑ava創(chuàng)建對象?
是的,使用構(gòu)造函數(shù),我們不應(yīng)該允許用戶每次嘗試訪問構(gòu)造函數(shù)并執(zhí)行它。
但是我們應(yīng)該這樣做一次,至少要得到一個對象。
那么,如何確保構(gòu)造函數(shù)只能訪問和執(zhí)行一次?
如何使它->如何防止類外部的方法訪問?
簡單,將make方法作為私有權(quán)限,類似地將構(gòu)造函數(shù)作為私有權(quán)限。
如何制作->這有多種實(shí)現(xiàn)方式,下面以示例來看。
如果滿足以上兩個條件,則我們班級將始終有一個對象。 該類稱為Singleton,因?yàn)樗谖覀冋埱蟮乃袝r間都產(chǎn)生單個對象。
沒有太多理論,我們現(xiàn)在將開始執(zhí)行它。
創(chuàng)建單例對象的方法有很多:
方法1
- 急于初始化或使用前初始化
EagerSingletonClass的實(shí)例在類啟動時創(chuàng)建。 由于它是靜態(tài)的,因此會在加載EagerSingletonClass的過程中加載并創(chuàng)建它。
- Junit測試類為上述類的單例測試。
優(yōu)勢:
此策略在加載類期間創(chuàng)建對象,因此從多線程方案中可以更快更安全。 我們只需要使實(shí)例具有可變性即可處理多線程方案。
壞處 :
這種策略會在類加載本身時創(chuàng)建實(shí)例,因此,如果我們不使用它,那么這將浪費(fèi)整個時間和內(nèi)存來創(chuàng)建實(shí)例。 因此,最好在需要時選擇一種策略來創(chuàng)建實(shí)例。
什么時候使用以上策略?
只要我們100%確保在我們的應(yīng)用程序中肯定使用了該對象。
要么 當(dāng)物體不重時,我們可以管理速度和內(nèi)存。
方法2
- 延遲初始化或在需要時初始化
與其在啟動時創(chuàng)建對象,不如在需要時創(chuàng)建對象,這是很好的。 因此,讓我們看看如何做到這一點(diǎn):
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;}}在以上程序中,僅當(dāng)通過getInstance()方法發(fā)出請求時,我們才創(chuàng)建了一個對象。
在此,在首次調(diào)用getInstance()的過程中,對象“ singletonInstance”將為null,并在條件變?yōu)閠rue時執(zhí)行if條件塊并創(chuàng)建一個對象。
然后,對getInstance()方法的后續(xù)調(diào)用將返回相同的object。
但是,如果我們看一下多線程方案,問題就出在以下上下文中:2個線程t1和t2調(diào)用getInstance()方法,線程t1執(zhí)行if(singletonInstance == null)并發(fā)現(xiàn)singletonInstance為null,因此它進(jìn)入同步塊以創(chuàng)建一個賓語。
但是在執(zhí)行對象創(chuàng)建邏輯之前,如果線程t2執(zhí)行if(singletonInstance == null),那么它還將發(fā)現(xiàn)singletonInstance為null,因此它還將嘗試輸入同步塊,但是由于已經(jīng)輸入的第一個線程t1,它沒有鎖。
因此,線程t2等待線程t1完成同步塊的執(zhí)行。
因此線程t1執(zhí)行并創(chuàng)建對象。 現(xiàn)在線程t2也正在等待同步塊時進(jìn)入同步塊,并再次創(chuàng)建對象。
因此,兩個線程創(chuàng)建了兩個對象。 因此無法實(shí)現(xiàn)單例。
解決上述問題的方法是“ 雙重檢查鎖定”。
它說在我們在同步塊內(nèi)執(zhí)行對象創(chuàng)建的邏輯之前,請重新檢查同步塊內(nèi)的實(shí)例變量。
這樣,我們可以避免多個線程多次創(chuàng)建對象。
怎么樣 ?
線程t1檢查條件if(singletonInstance == null),第一次為true,因此它進(jìn)入同步塊,然后再次檢查條件if(singletonInstance == null),這也為true,因此創(chuàng)建了對象。
現(xiàn)在線程t2進(jìn)入方法getInstance()并假定它已在線程t1執(zhí)行對象創(chuàng)建邏輯之前執(zhí)行了if(singletonInstance == null)條件,然后t2也等待進(jìn)入同步塊。
在線程t1從同步塊中出來之后,線程t2進(jìn)入了同一個塊,但是我們再次在其中有if條件if(singletonInstance == null)但線程t1已經(jīng)創(chuàng)建了一個對象,它使條件變?yōu)閒alse并進(jìn)一步停止執(zhí)行并返回相同的實(shí)例。
讓我們看看如何在代碼中完成它:
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");}}上面的實(shí)現(xiàn)是針對單例模式的最佳建議解決方案,它最適合于單線程,多線程等所有情況。
方法3
- 使用內(nèi)部類的單例
讓我們看一下下面的使用內(nèi)部類創(chuàng)建對象的代碼:
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);}}以上使用內(nèi)部類創(chuàng)建對象的方法是創(chuàng)建單例對象的最佳方法之一。
在這里,除非并且直到有人嘗試訪問LazySingleton靜態(tài)內(nèi)部類的靜態(tài)引用變量,否則將不會創(chuàng)建該對象。
因此,這還將確保在需要時以及在需要時創(chuàng)建對象。 而且實(shí)現(xiàn)起來非常簡單。 從多線程進(jìn)行也是安全的。
方法4
- 具有序列化和反序列化的Singleton
現(xiàn)在假設(shè)我們的應(yīng)用程序是分布式的,我們序列化我們的單例對象并將其寫入文件。 后來我們通過反序列化單例對象來閱讀它。 取消序列化對象始終會創(chuàng)建一個文件內(nèi)具有可用狀態(tài)的新對象。 如果在寫入文件后進(jìn)行任何狀態(tài)更改,然后嘗試取消序列化對象,則將獲得原始對象,而不是新的狀態(tài)對象。 因此,在此過程中我們得到了2個對象。
讓我們嘗試通過程序來了解這個問題:
首先->使Singleton類可序列化以序列化和反序列化此類的對象。
第二件事->將對象寫入文件(序列化)
第三件事->更改對象狀態(tài) 第四件事-> 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;}}序列化我們的對象,然后對狀態(tài)進(jìn)行一些更改,然后取消序列化。
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個不同的對象。 之所以發(fā)生這種情況,是因?yàn)榉葱蛄谢瘯?chuàng)建具有文件中可用狀態(tài)的新實(shí)例。
如何克服這個問題? 意味著如何防止在反序列化期間創(chuàng)建新實(shí)例?
解決方案非常簡單–在您的singleton類中實(shí)現(xiàn)以下方法:
Access_modifier Object readResolve() throws ObjectStreamException{ }例:
Public Object readResolve() throws ObjectStreamException{ return modifiedInstance; }將其應(yīng)用于上面的單例課程,則完整的單例課程如下:
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;}}現(xiàn)在運(yùn)行上面的序列化和反序列化類,以檢查兩個實(shí)例的輸出。
輸出:
200
200
這是因?yàn)?#xff0c;在反序列化期間,它將調(diào)用readResolve()方法,并且在此返回現(xiàn)有實(shí)例,這將阻止創(chuàng)建新實(shí)例并確保單例對象。
- 小心序列號
在序列化之后和取消序列化之前,只要類結(jié)構(gòu)發(fā)生更改。 然后,在反序列化過程中,它將找到一個不兼容的類,并因此引發(fā)異常:java.io.InvalidClassException:SingletonClass; 本地類不兼容:流classdesc serialVersionUID = 5026910492258526905,本地類serialVersionUID = 3597984220566440782
因此,為避免發(fā)生此異常,我們必須始終對可序列化的類使用序列號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
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Java Singleton设计模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux与或非逻辑符号(linux 与
- 下一篇: 安卓 手游 下载(安卓版手机游戏下载)