单例设计模式–鸟瞰
幾天前,當我回到家鄉時,我的一位來自同事的大三學生參加了一家跨國公司的采訪,在采訪過程中受了重傷。 我的意思是,由于面試小組提出的難題,他無法使面試合格。 當我回到班加羅爾時,他分享了他在技術面試中遇到的尷尬處境。 根據他今天的經驗,我正在撰寫有關Singleton設計模式的文章。 順便說一下,我的下級同事在Java方面擁有近四年的經驗。 他面臨的一個有爭議的問題是“ 什么是Singleton設計模式,您將如何編寫健壯的Singleton類? ”但是,讓我給您提供在項目/產品開發時經常使用的Singleton設計模式的基本和關鍵輪廓。
如您所知,Singleton設計模式屬于“ Creational Pattern ”類別。 基本原則說,在任何時間點,一個類都應該只有一個實例,而與一個類的多次調用無關。 此原理背后有許多概念和設計。 許多開發人員采用不同的方式在Java中實現Singleton。 一些開發人員根本不喜歡這種設計。 但是,我們應該專注于這種設計,而其他因素對我們而言則完全不相關。 讓我們從各種角度分析此設計。
技術性
正如我已經提到的,將有一個類的實例,讓我們看下面的代碼。
package com.ddlab.rnd.patterns; public class SingletonType1 {private static SingletonType1 instance = null;private SingletonType1(){super();}public static SingletonType1 getInstance(){if( instance == null )instance = new SingletonType1();return instance;} }要使用和實現,我們必須編寫以下代碼。
SingletonType1 instance = SingletonType1.getInstance();很好,似乎是正確的。 如果您編寫10次以上的代碼,您將獲得相同的實例。 檢查以上程序的正確性。 讓我們做一個基本的臨床測試。 通過調用代碼“ SingletonType1.getInstance()”來創建上述類的實例,并將所有實例放入Set中。 如您所知,Set不允許重復。 因此,最后,如果獲得集合1的大小,則它是正確的實現。 您也可以。 肯定會得到結果為1,即Set的大小。 現在我們想到了一個問題,我們可以打破以上設計嗎? 是否可以創建上述定義的類的多個實例? 是。 我們可以。 我們可以打破以上設計,并可以創建多個實例。 那個怎么樣 ????????
讓我們看下面的代碼。
package com.ddlab.rnd.patterns; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;public class TestSingletonType1 {public void createMultiInstances(){System.out.println("\n** MULTIPLE INSTANCES FROM SINGLETO **\n");/** Using Reflection you can break singleton*/try {Class clazz = Class.forName("com.ddlab.rnd.patterns.SingletonType1");Constructor constructor = clazz.getDeclaredConstructors()[0];constructor.setAccessible(true);SingletonType1 instance1 = (SingletonType1)constructor.newInstance(null);SingletonType1 instance2 = (SingletonType1)constructor.newInstance(null);SingletonType1 instance3 = (SingletonType1)constructor.newInstance(null);System.out.printf( "%-15s %-15s %n", "SERIAL NO", "MULTI INSTANCES");System.out.printf( "%-15s %-15s %n", "---------", "---------------");System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);}catch (Exception e) {e.printStackTrace();}}public void createMultiInstances1(){System.out.println("\n********* MULTIPLE INSTANCES FROM SINGLETON ********\n");/** Using Reflection you can break singleton*/try {Class clazz = Class.forName("com.ddlab.rnd.patterns.SingletonType1");Method method = clazz.getDeclaredMethods()[0];Field field = clazz.getDeclaredFields()[0];field.setAccessible(true);SingletonType1 instance1 = (SingletonType1)method.invoke(clazz, null);field.set(clazz, null);SingletonType1 instance2 = (SingletonType1)method.invoke(clazz, null);field.set(clazz, null);SingletonType1 instance3 = (SingletonType1)method.invoke(clazz, null);System.out.printf( "%-15s %-15s %n", "SERIAL NO", "MULTI INSTANCES");System.out.printf( "%-15s %-15s %n", "---------", "---------------");System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);}catch (Exception e) {e.printStackTrace();}}public void createInstances(){System.out.println("\n*********** SINGLE INSTANCES FROM SINGLETON ********\n");SingletonType1 instance1 = SingletonType1.getInstance();SingletonType1 instance2 = SingletonType1.getInstance();SingletonType1 instance3 = SingletonType1.getInstance();System.out.printf( "%-15s %-15s %n", "SERIAL NO", "INSTANCES");System.out.printf( "%-15s %-15s %n", "---------", "----------");System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);}public static void main(String[] args) {new TestSingletonType1().createInstances();new TestSingletonType1().createMultiInstances();new TestSingletonType1().createMultiInstances1();}}如果運行上面的程序,您將能夠看到已定義的singleton類的許多實例。
但是我們知道,可以使用反射來破壞Singleton的私有構造方法。 在上述情況下,我們可以創建具有私有構造函數的類的實例,也可以訪問私有字段。 哦,是的。。。您真的創建了多個實例嗎?是的,BOSS,我做了,您覺得呢? 您以任何方式構建設計,但我可能會破壞。 確實,這傷害了像我這樣真正的情感開發者的情緒。 OKKkkk。 現在,我將編寫一個非常有效的代碼,這樣您就不會崩潰。 真的是…….. ???????? 學習Java Relection機制對于探索JAVA的美麗至關重要。
現在讓我們看看如何編寫更好的代碼,以便其他開發人員將無法使用反射來破壞代碼。
package com.ddlab.rnd.patterns; import java.lang.reflect.ReflectPermission; import java.security.Permission;public class SingletonType2 {static{getInstance();}private static SingletonType2 instance = null;private SingletonType2(){super();//Add the following piece of code so that it can not be invoked using relectionSystem.setSecurityManager(new SecurityManager() {@Overridepublic void checkPermission(Permission perm) {if (perm instanceof ReflectPermission ){System.out.println("\nYes I will not allow you to create the instance using Reflection...\n");throw new SecurityException();}else{//Do nothing}}});}public static SingletonType2 getInstance(){if( instance == null )instance = new SingletonType2();return instance;} }現在確實如此,您的反射攻擊將不會影響上述代碼。 如果使用反射創建另一個實例,則將在此處獲得Exception。 您可以考慮一下,使用Java自省實用程序可能會破壞它。 您可能還認為,我們將不會訪問構造函數,而是將訪問該字段,然后將字段值設置為null,然后再次調用該字段。 這是一個很好的策略,但是您會失敗,因為自省實用程序是另一種反映。 由于我們不允許反射,因此您將無法創建多個實例。 但是,您仍然可以使用自省來調用方法“ getInstance()”,但是您將獲得相同的實例。 因此,我們的反思和內省思想可以在這種情況下被拋棄。 讓我們以不同的方式思考,指向類的序列化。 那么如果我們要序列化會發生什么呢? 在上面的類中,您不能進行繼承,因為構造函數是私有的;對于防彈機制,您可以將類定為最終類。 我們無法序列化SingletonType2類,因為它沒有實現Serializable接口,并且我們也不允許反射。 但是,我們無法序列化未實現Serilizable接口的類。 但是,有時需要將Singleton對象保留一天。 在這種情況下,我們必須在單例類中實現Serializable接口。 現在我們的項目或產品需要序列化,并且我們將不使用SecurityManager概念。 讓我們修改上面的類。
讓我們看看帶有Seri??alizable接口的Singleton類。
package com.ddlab.rnd.patterns; import java.io.Serializable;public class SingletonType11 implements Serializable {private static final long serialVersionUID = -4137189065490862968L;private static SingletonType11 instance = null;private SingletonType11(){super();}public static SingletonType11 getInstance(){if( instance == null )instance = new SingletonType11();return instance;} }好的,我們將能夠序列化上面的類,但是我們再次為黑客提供了創建多個實例的機會,因此我們的概念再次在這里中斷。 讓我們看看如何通過對象序列化再次打破Singleton的概念。 讓我們用這樣編寫一個小類。
package com.ddlab.rnd.patterns; import java.io.Serializable;public class BreakSingleton implements Serializable {private static final long serialVersionUID = 5904306999023481976L;private SingletonType11 instance2 = SingletonType11.getInstance();public SingletonType11 getInstance2() {return instance2;}public void setInstance1(SingletonType11 instance2) {this.instance2 = instance2;} }讓我們看看上面的測試工具類。
package com.ddlab.rnd.patterns; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream;public class TestBreakSingleton {public static void main(String[] args) throws Exception{BreakSingleton bs = new BreakSingleton();OutputStream out = new FileOutputStream("data/a.ser");ObjectOutputStream oout = new ObjectOutputStream(out);oout.writeObject(bs);oout.flush();oout.close();out.flush();out.close();InputStream in = new FileInputStream("data/a.ser");ObjectInputStream oin = new ObjectInputStream(in);BreakSingleton bs1 = (BreakSingleton)oin.readObject();oin.close();in.close();System.out.println("Instance from Serialization :::"+bs1.getInstance2());System.out.println("Normal Instance :::"+SingletonType11.getInstance());InputStream in1 = new FileInputStream("data/a.ser");ObjectInputStream oin1 = new ObjectInputStream(in1);BreakSingleton bs2 = (BreakSingleton)oin1.readObject();oin1.close();in1.close();System.out.println("Another Instance from Serialization :::"+bs2.getInstance2());}}如果運行上述程序,則將獲得以下類型的輸出。
Instance from Serialization :::com.ddlab.rnd.patterns.SingletonType11@2586db54Normal Instance :::com.ddlab.rnd.patterns.SingletonType11@12276af2Another Instance from Serialization :::com.ddlab.rnd.patterns.SingletonType11@38a97b0b因此,現在您獲得了Singleton類的三個不同實例。 同樣,我們遇到了多個實例的問題。 有什么辦法可以使我們不會給黑客機會創建多個實例,而是可以序列化該對象? 哦,是的,有。 現在,讓我們看一下修改后的單例Java類,以便我們能夠序列化該對象,并且在任何時間點我們都將獲得一致的單例類。
package com.ddlab.rnd.patterns; import java.io.ObjectStreamException; import java.io.Serializable;public class SingletonType11 implements Serializable {private static final long serialVersionUID = -4137189065490862968L;private static SingletonType11 instance = null;private SingletonType11(){super();}public static SingletonType11 getInstance(){if( instance == null )instance = new SingletonType11();return instance;}private Object readResolve() throws ObjectStreamException{return instance;}private Object writeReplace() throws ObjectStreamException{return instance;} }在上述方法中,我們將從序列化對象和“ getInstance()”方法的常規調用中獲得一致的單例對象。 但是,我們仍然可以使用Reflection創建多個實例,并且由于要序列化對象而無法防止反射。 在這種情況下,我們可以向開發人員提出請求并達成協議,不要僅使用反射來避免反射策略。 開發人員達成了一項協議,不要使用反思來打破。
那么多線程或在多線程應用程序中使用單例呢? 讓我們看看這里發生了什么。 讓我們看看在Singleton類的情況下線程的使用。
讓我們考慮一下我們前面討論的第一個Singleton類。
package com.ddlab.rnd.patterns; public class SingletonType1 {private static SingletonType1 instance = null;private SingletonType1(){super();}public static SingletonType1 getInstance(){if( instance == null )instance = new SingletonType1();return instance;} }基本上,這種方法稱為延遲初始化。 在多線程的情況下,如果處理不當,我們可以獲得多個實例。 讓我們看下面的代碼。
package com.ddlab.rnd.patterns; import java.util.Collections; import java.util.HashSet; import java.util.Set;class Thread1 extends Thread {@Overridepublic void run() {SingletonType1 instance = SingletonType1.getInstance();// System.out.println("In Thread 1 - Singleton Instance ---->"+instance);TestSingletonType1_Thread.singletonSet.add(instance);} }class Thread2 extends Thread {@Overridepublic void run() {SingletonType1 instance = SingletonType1.getInstance();// System.out.println("In Thread 2 - Singleton Instance ---->"+instance);TestSingletonType1_Thread.singletonSet.add(instance);} }讓我們看看測試類如何使用它。
public class TestSingletonType1_Thread {private static Set singletonSet1 = new HashSet();public static Set singletonSet = Collections.synchronizedSet(singletonSet1);public static void main(String[] args) {//Singleton concept is broken herefor( int i = 0 ; i < 100 ; i++ ){new Thread1().start();new Thread2().start();if( singletonSet.size() > 1 )break;elsecontinue;}System.out.println(singletonSet);} }如果您多次運行上述程序,則將獲得Singleton類的不同實例。
運行該程序后,您可能會得到類似的結果。 輸出如下。
[com.ddlab.rnd.patterns.SingletonType1@60723d7c, com.ddlab.rnd.patterns.SingletonType1@6d9efb05, com.ddlab.rnd.patterns.SingletonType1@8dd20f6]那么該怎么辦 ? 我們可以聲明volatile變量,現在讓我們看看。 讓我們擁有修改后的程序。
package com.ddlab.rnd.patterns; public class SingletonType1 {private static volatile SingletonType1 instance = null;private SingletonType1(){super();}public static SingletonType1 getInstance(){if( instance == null )instance = new SingletonType1();return instance;} }在多次運行該程序后,您可能會得到這樣的信息。
[com.ddlab.rnd.patterns.SingletonType1@3f0ef90c, com.ddlab.rnd.patterns.SingletonType1@2e471e30]但是,使用volatile不能滿足我們的目的。 還是同樣的問題,我們可以使用同步方法嗎,是的,我們可以做到。 在許多情況下,大多數開發人員會提出與volatile關鍵字的用法及其在Singleton中的用法有關的問題。 如果經驗不足的開發人員在其計算機上的第一次運行中獲得上述類的單個實例,則可能會感到高興。 我有很多開發人員在他們的機器上運行該程序是有道理的,他們也向我展示了。 這是正確的,因為他們很幸運。 但是我在他們的機器上多次運行了該程序,并告訴他們不要忘記事實。 現在,他們中的許多人開始使用java的great關鍵字和“同步的”生命保護程序來修改程序。 讓我們看看這個關鍵字會發生什么。
讓我們在下面看到。
package com.ddlab.rnd.patterns; public class SingletonType1 {private static volatile SingletonType1 instance = null;private SingletonType1(){super();}public static synchronized SingletonType1 getInstance(){if( instance == null )instance = new SingletonType1();return instance;} }但是會出現性能問題。 但是,當您對其進行分析時,您將意識到僅在第一次調用該方法時才需要同步。 后續調用不需要同步。 因此,不建議在每次調用時都使用關鍵字“ synchronized”。 從長遠來看,它可能會對您的產品/項目開發產生不利影響。 為了提高上述程序的效率,讓我們以不同的方式修改上述程序。 我們不會同步整個方法,而是會做敏感區域。
package com.ddlab.rnd.patterns; public class SingletonType1 {private static volatile SingletonType1 instance = null;private SingletonType1(){super();}public static SingletonType1 getInstance(){if (instance == null){synchronized(SingletonType1.class) {instance = new SingletonType1();}}return instance;} }上面的程序看起來還不錯,我們很高興,現在讓我們慶祝。 但是,由于存在一個非常大的問題,我們仍然遠離艱苦的現實。 當instance為null時,兩個線程可以同時進入if語句內部。 然后,一個線程進入同步塊以初始化實例,而另一個則被阻塞。 當第一個線程退出同步塊時,等待線程進入并創建另一個Singleton對象。 請注意,當第二個線程進入同步塊時,它不會檢查實例是否為非空。 讓我們做一個小的臨床測試來面對現實。
package com.ddlab.rnd.patterns; import java.util.Collections; import java.util.HashSet; import java.util.Set;class Thread11 extends Thread {@Overridepublic void run() {SingletonType111 instance = SingletonType111.getInstance();// System.out.println("In Thread 1 - Singleton Instance ---->"+instance);TestSingletonType111_Thread.singletonSet.add(instance);} }class Thread22 extends Thread {@Overridepublic void run() {SingletonType111 instance = SingletonType111.getInstance();// System.out.println("In Thread 2 - Singleton Instance ---->"+instance);TestSingletonType111_Thread.singletonSet.add(instance);} }public class TestSingletonType111_Thread {private static Set singletonSet1 = new HashSet();public static Set singletonSet = Collections.synchronizedSet(singletonSet1);public static void main(String[] args) {//Singleton concept is broken herefor( int i = 0 ; i < 100 ; i++ ){new Thread11().start();new Thread22().start();if( singletonSet.size() > 1 )break;elsecontinue;}System.out.println(singletonSet);}}現在,您將多次了解上述程序。 接下來要做什么。
現在讓我們考慮另一個被稱為“雙重檢查鎖定”的概念,該概念似乎對于一組開發人員來說是著名的。 許多開發人員在許多情況下都適用,并認為這是最強大的單例形式。
在軟件工程中,雙重檢查鎖定(也稱為“雙重檢查鎖定優化”)是一種軟件設計模式,用于通過首先測試鎖定標準(“鎖定提示”)來減少獲取鎖定的開銷,而無需實際獲取鎖。 只有在鎖定
標準檢查表明是否需要鎖定,實際的鎖定邏輯是否繼續進行。 在大多數技術面試中,技術小組都希望候選人能給出這個答案。 如果候選人能夠根據自己的喜好回答此問題,技術小組將很高興并選擇候選人。 如今,這個概念已變得非常重要,但是我要說的是技術小組對此概念沒有足夠的經驗。 讓我們對其進行深入分析。 “雙重檢查鎖定”的基本結構如下。
public static SingletonType1 getInstance() {if (instance == null){synchronized(SingletonType1.class) // Mark - 1{ if (instance == null) // Mark - 2instance = new SingletonType1(); // Mark - 3}}return instance; }雙重檢查鎖定背后的理論是// // Mark – 2處的第二次檢查使得不可能創建兩個不同的Singleton對象。 好的... 對于單線程應用程序可能是正確的。 細粒度的多線程應用程序呢? 讓我們看下面的順序。
線程1進入getInstance()方法。
線程1在// Mark – 1處進入同步塊,因為實例為空。
線程1被線程2搶占。
線程2進入getInstance()方法。
線程2嘗試獲取// Mark – 1處的鎖,因為實例仍然為null。 但是,由于線程1持有該鎖,因此線程2在// Mark – 1處阻塞。
線程2被線程1搶占。
執行線程1,并且由于在// Mark – 2處instance仍然為空,因此創建了Singleton對象并將其引用分配給實例。
線程1退出同步塊,并從getInstance()方法返回實例。
線程1被線程2搶占。
線程2獲取// // Mark – 1處的鎖,并檢查instance是否為null。
由于instance非null,因此不會創建第二個Singleton對象,并且將返回線程1創建的對象。 雙重檢查鎖定背后的理論是完美的。 不幸的是,現實是完全不同的。 雙重檢查鎖定的問題在于不能保證它可以在單處理器或多處理器計算機上運行。 雙重檢查鎖定失敗的問題不是由于JVM中的實現錯誤,而是由于當前的Java平臺內存模型。 內存模型允許所謂的“亂序寫入”,這是該成語失敗的主要原因。 但是,“亂序寫入”的概念超出了我們的討論范圍。 最重要的是,不應使用任何形式的雙重檢查鎖定,因為您不能保證它可以在任何JVM實現中使用。 如我們所見,雖然“雙重檢查鎖定”可能有效,但可能會意外失敗。 解決辦法是什么 ?
Bill Pugh的解決方案
馬里蘭大學計算機科學研究員Bill Pugh(摘自Wikipedia)撰寫了有關用Java實現Singleton模式的代碼問題。 Pugh對“雙重檢查鎖定”這一習慣用法的努力導致了Java 5中Java內存模型的變化,并導致了通常被視為在Java中實現Singletons的標準方法。 這種技術稱為按需初始化持有人慣用語,它盡可能懶惰,并且可以在所有已知的Java版本中使用。 它利用了有關類初始化的語言保證,因此可以在所有Java兼容的編譯器和虛擬機中正常工作。 嵌套類的引用不早于調用getInstance()的時間(因此,類加載器不會更早地對其進行加載)。 因此,該解決方案是線程安全的,不需要特殊的語言構造(即易失性或同步的)。
public class Singleton {// Private constructor prevents instantiation from other classesprivate Singleton() { }/*** SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before.*/private static class SingletonHolder { public static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;} }上面稱為“ 按需初始化持有人習慣用法 ”。 Singleton設計的上述結構在高度多線程的應用程序中非常強大。 讓我們詳細了解這個概念。 讓我們考慮一個下面的小例子。
public class Something {private Something() {}private static class LazyHolder{public static final Something INSTANCE = new Something();}public static Something getInstance() {return LazyHolder.INSTANCE;} }怎么運行的
該實現依賴于Java虛擬機(JVM)內指定的執行初始化階段。 有關詳細信息,請參見Java語言規范(JLS)的12.4節。 當JVM加載Something類時,該類將進行初始化。 由于該類沒有任何要初始化的靜態變量,因此初始化很容易完成。 在JVM確定必須執行LazyHolder之前,不會初始化其中的靜態類定義LazyHolder。 僅當在Something類上調用靜態方法getInstance時,才執行靜態類LazyHolder,并且第一次發生這種情況時,JVM將加載并初始化LazyHolder類。 LazyHolder類的初始化導致靜態變量INSTANCE的執行是通過對外部類Something執行(私有)構造函數來進行的。 由于JLS保證類的初始化階段是串行的,即非并發的,因此在加載和初始化期間,靜態getInstance方法中不需要進一步的同步。 并且由于初始化階段在串行操作中寫入了靜態變量INSTANCE,因此對getInstance的所有后續并發調用將返回相同的正確初始化的INSTANCE,而不會引起任何其他同步開銷。
但是,使用“按需初始化持有人慣用語”模式的概念,我們可以實現線程安全的單例構造。 再次出現問題,我們可以反思地打破嗎。 是的,我們可以使用我已經提到的java反射機制打破上述概念。 現在問題來了,是否還有其他方法可以構建適當的單例設計方法。 是的, Joshua Bloch(Google技術實驗室首席技術架構師和著名的Book Effective Java的作者)建議使用另一種方法。
package com.ddlab.rnd.patterns; public enum SingletonType3 {INSTANCE;public void doSomething(String arg) {//... perform operation here ...} }這是創建單例類的唯一可靠方法,該類是可序列化的,并且在默認情況下是完全線程安全的,而枚舉是完全線程安全的。 關于反射,使用上面的反射方法,您不能破壞單例對象,因為它沒有構造函數。 關于序列化,您將能夠對其進行序列化,但是每次都會獲得相同的實例。 因此,最后我們必須吸收這種創建Singleton設計類的現代方法。 但是,許多開發人員對此一無所知。
但是,大多數訪調員不會接受以上兩種方法,因為對他們而言,這可能是一個新概念。 您可以基于JLS和書籍參考進行辯論。 我的大三學生,同事和朋友每天都抱怨,在面試時這是他們通常在面試時面臨的最困難的問題。 無論他們以何種方式回答問題,面試官都不會感到滿意,這是因為在單例設計課程中,大多數人都不了解枚舉的方法。 如果您也遇到相同的問題,請舉約書亞·布洛赫(Joshua Bloch)為例。 您可能會遇到一些開發人員或訪問員的問題,即“ Singleton類必須具有私有構造函數,并且應該有一個名為getInstance()的方法”。 您必須論證說下劃線語句是錯誤的,并且它不是協議或任何經驗法則。 這只是我們一段時間以來采用的一種方法。 單例背后的主要概念是在任何時間點,都應該只有一個實例,與如何編寫代碼無關。 如果面試不斷與您爭論,您會問他將枚舉定義為單例方法的問題所在。 膽小的面試官可能會提出一些無稽之談。 最后,您告訴他,在JDK 5中,枚舉由Josh Bloch和Neal Gafter編寫。 如果開發人員或面試官有膽量,他可以將郵件發送給這些優秀的建筑師。 如果傲慢的面試官仍在作出錯誤的論點,請教給他一堂課,“先生,您告訴我單身人士的做法,這是無法打破的。 至少我會以各種方式破壞您的Singleton設計。”
仍然不能使用枚舉破壞上述單例方法,但是我們可以通過編寫代碼來創建多個實例來破解上述方法。 下面給出的代碼請勿將以下代碼用于您的商業產品。 這是打破單例的討厭方法。 讓我們看下面的代碼。
package com.ddlab.rnd.patterns; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import sun.reflect.ConstructorAccessor;public class CrackEnumSingleton {public static void main(String[] args){Set set = new HashSet();try {SingletonType3 firstInstance = SingletonType3.INSTANCE;System.out.println(firstInstance.getClass() + " " + firstInstance + " = " + System.identityHashCode(firstInstance));set.add(firstInstance);Constructor constructor = SingletonType3.class.getDeclaredConstructors()[0];Method acquire = constructor.getClass().getDeclaredMethod("acquireConstructorAccessor");//"acquireConstructorAccessor" fields for crackingacquire.setAccessible(true);acquire.invoke(constructor);Method get = constructor.getClass().getDeclaredMethod("getConstructorAccessor");//"getConstructorAccessor" fields for crackingget.setAccessible(true);ConstructorAccessor invoke = (ConstructorAccessor) get.invoke(constructor);Object secondInstance = invoke.newInstance(new Object[] {null,1});System.out.println(secondInstance.getClass() + " " + secondInstance + " = " + System.identityHashCode(secondInstance));set.add(secondInstance);System.out.println("Total No of Singletons :::"+set.size());}catch (Exception e) {e.printStackTrace();}}}但是,上述方法只是一種學習技術,而并非在任何地方實現。 在這個世界上,每個對象都具有積極和消極的態度,但是我們必須遵循積極的態度才能順利開發我們的產品或項目。
結論
本文沒有任何商業意義。 在本文中,我提供了編寫更好的Singleton設計類的更好方法。 可能有最佳方法,如果您知道其他最佳方法或最佳做法,請與我分享。 還提供一些注釋,以便我們可以為更好的編碼標準做出更好的貢獻。 希望您喜歡我的文章。 如有任何錯誤,請通過debadatta.mishra@gmail.com向我報告。 謝謝。
翻譯自: https://www.javacodegeeks.com/2013/06/singleton-design-pattern-a-lions-eye-view.html
總結
- 上一篇: 顽皮狗总裁 Neil Druckmann
- 下一篇: 腾讯混元大模型已开始内测 近期将正式对外