透视变换–鸟瞰图_单例设计模式–鸟瞰
透視變換–鳥(niǎo)瞰圖
幾天前,當(dāng)我回到家鄉(xiāng)時(shí),我的一位來(lái)自同事的準(zhǔn)青年參加了一家跨國(guó)公司的采訪,在采訪過(guò)程中受了重傷。 我的意思是,由于面試小組提出了一些難題,他無(wú)法使面試合格。 當(dāng)我回到班加羅爾時(shí),他分享了他在技術(shù)面試中遇到的尷尬處境。 根據(jù)他今天的經(jīng)驗(yàn),我正在撰寫有關(guān)Singleton設(shè)計(jì)模式的文章。 順便說(shuō)一下,我的下級(jí)同事在Java方面擁有近四年的經(jīng)驗(yàn)。 他面臨的一個(gè)有爭(zhēng)議的問(wèn)題是“ 什么是Singleton設(shè)計(jì)模式,您將如何編寫健壯的Singleton類? ”但是,讓我給您提供在項(xiàng)目/產(chǎn)品開(kāi)發(fā)時(shí)經(jīng)常使用的Singleton設(shè)計(jì)模式的基本和關(guān)鍵輪廓。
如您所知,Singleton設(shè)計(jì)模式屬于“ Creational Pattern ”類別。 基本原則說(shuō),在任何時(shí)間點(diǎn),一個(gè)類都應(yīng)該只有一個(gè)實(shí)例,而與一個(gè)類的多次調(diào)用無(wú)關(guān)。 該原理背后有許多概念和設(shè)計(jì)。 許多開(kāi)發(fā)人員采用不同的方式在Java中實(shí)現(xiàn)Singleton。 一些開(kāi)發(fā)人員根本不喜歡這種設(shè)計(jì)。 但是,我們應(yīng)該專注于這種設(shè)計(jì),而其他因素對(duì)我們來(lái)說(shuō)則完全不相關(guān)。 讓我們從各種角度分析此設(shè)計(jì)。
技術(shù)性
正如我已經(jīng)提到的,將有一個(gè)類的實(shí)例,讓我們看下面的代碼。
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;} }要使用和實(shí)現(xiàn),我們必須編寫以下代碼。
SingletonType1 instance = SingletonType1.getInstance();很好,似乎是正確的。 如果您編寫10次以上的代碼,您將獲得相同的實(shí)例。 檢查以上程序的正確性。 讓我們做一個(gè)基本的臨床測(cè)試。 通過(guò)調(diào)用代碼“ SingletonType1.getInstance()”來(lái)創(chuàng)建上述類的實(shí)例,并將所有實(shí)例放入Set中。 如您所知,Set不允許重復(fù)。 因此,最后如果獲得集合1的大小,則它是正確的實(shí)現(xiàn)。 您也可以。 肯定會(huì)得到結(jié)果為1,即Set的大小。 現(xiàn)在我們想到了一個(gè)問(wèn)題,我們可以打破以上設(shè)計(jì)嗎? 是否可以創(chuàng)建上述定義的類的多個(gè)實(shí)例? 是。 我們可以。 我們可以打破以上設(shè)計(jì),并且可以創(chuàng)建多個(gè)實(shí)例。 那個(gè)怎么樣 ????????
讓我們看下面的代碼。
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();}}如果運(yùn)行上述程序,您將能夠看到已定義的singleton類的許多實(shí)例。
但是我們知道,可以使用反射來(lái)破壞Singleton的私有構(gòu)造方法。 在上述情況下,我們可以創(chuàng)建具有私有構(gòu)造函數(shù)的類的實(shí)例,也可以訪問(wèn)私有字段。 哦,是的。。。您真的創(chuàng)建了多個(gè)實(shí)例嗎?是的,BOSS,我做了,您覺(jué)得呢? 您以任何方式構(gòu)建設(shè)計(jì),但我可能會(huì)破壞。 確實(shí),這傷害了像我這樣真正的情感開(kāi)發(fā)者的情緒。 OKKkkk。 現(xiàn)在,我將編寫一個(gè)非常有效的代碼,這樣您就不會(huì)崩潰。 真的是…….. ???????? 學(xué)習(xí)Java Relection機(jī)制對(duì)于探索JAVA的美麗至關(guān)重要。
現(xiàn)在,讓我們看看如何編寫更好的代碼,以便其他開(kāi)發(fā)人員將無(wú)法使用反射進(jìn)行破壞。
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;} }現(xiàn)在確實(shí)如此,您的反射攻擊將不會(huì)影響上述代碼。 如果使用反射創(chuàng)建另一個(gè)實(shí)例,則將在此處獲得Exception。 您可以考慮一下,使用Java自省實(shí)用程序可能會(huì)破壞它。 您可能還認(rèn)為,我們將不會(huì)訪問(wèn)構(gòu)造函數(shù),而是將訪問(wèn)該字段,然后將字段值設(shè)置為null,然后再次調(diào)用該字段。 這是一個(gè)很好的策略,但是您會(huì)失敗,因?yàn)樽允?shí)用程序是另一種反映。 由于我們不允許反射,因此您將無(wú)法創(chuàng)建多個(gè)實(shí)例。 但是,您仍然可以使用自省來(lái)調(diào)用方法“ getInstance()”,但是您將獲得相同的實(shí)例。 因此,我們?cè)谶@種情況下可以省去反思和反思的想法。 讓我們以不同的方式思考,指向類的序列化。 那么如果我們要序列化會(huì)發(fā)生什么呢? 在上面的類中,您不能繼承,因?yàn)闃?gòu)造函數(shù)是私有的;對(duì)于防彈機(jī)制,您可以將類定為最終類。 我們無(wú)法序列化SingletonType2類,因?yàn)樗鼪](méi)有實(shí)現(xiàn)Serializable接口,并且我們也不允許反射。 但是,我們無(wú)法序列化未實(shí)現(xiàn)Serilizable接口的類。 但是,有時(shí)需要將Singleton對(duì)象保留一天。 在這種情況下,我們必須在單例類中實(shí)現(xiàn)Serializable接口。 現(xiàn)在我們的項(xiàng)目或產(chǎn)品需要序列化,并且我們將不使用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;} }好的,我們將能夠序列化上面的類,但是我們?cè)俅螢楹诳吞峁┝藙?chuàng)建多個(gè)實(shí)例的機(jī)會(huì),因此我們的概念再次在這里中斷。 讓我們看看如何通過(guò)對(duì)象序列化再次打破Singleton的概念。 讓我們用這樣編寫一個(gè)小類。
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;} }讓我們看看上面的測(cè)試工具類。
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());}}如果運(yùn)行上述程序,則將獲得以下類型的輸出。
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因此,現(xiàn)在您獲得了Singleton類的三個(gè)不同實(shí)例。 同樣,我們遇到了多個(gè)實(shí)例的問(wèn)題。 有什么辦法可以使我們不會(huì)給黑客機(jī)會(huì)創(chuàng)建多個(gè)實(shí)例,而是可以序列化該對(duì)象? 哦,是的,有。 現(xiàn)在,讓我們看一下修改后的單例Java類,以便能夠序列化該對(duì)象,并且在任何時(shí)間點(diǎn)都將獲得一致的單例類。
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;} }在上述方法中,我們將從序列化對(duì)象和“ getInstance()”方法的常規(guī)調(diào)用中獲得一致的單例對(duì)象。 但是,我們?nèi)匀豢梢允褂肦eflection創(chuàng)建多個(gè)實(shí)例,并且由于要序列化對(duì)象而無(wú)法防止反射。 在這種情況下,我們可以向開(kāi)發(fā)人員提出請(qǐng)求并達(dá)成協(xié)議,不要僅使用反射來(lái)避免反射策略。 開(kāi)發(fā)人員達(dá)成了一項(xiàng)協(xié)議,即不破壞使用反射。
那么多線程或在多線程應(yīng)用程序中使用單例呢? 讓我們看看這里發(fā)生了什么。 讓我們看看在Singleton類的情況下線程的使用。
讓我們考慮一下我們前面討論的第一個(gè)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;} }基本上,這種方法稱為延遲初始化。 在多線程的情況下,如果處理不當(dāng),我們可以獲得多個(gè)實(shí)例。 讓我們看下面的代碼。
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);} }讓我們看看測(cè)試類如何使用它。
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);} }如果您多次運(yùn)行上述程序,則將獲得Singleton類的不同實(shí)例。
運(yùn)行該程序后,您可能會(huì)得到類似的結(jié)果。 輸出如下。
[com.ddlab.rnd.patterns.SingletonType1@60723d7c, com.ddlab.rnd.patterns.SingletonType1@6d9efb05, com.ddlab.rnd.patterns.SingletonType1@8dd20f6]那么該怎么辦 ? 我們可以聲明volatile變量,現(xiàn)在讓我們看看。 讓我們擁有修改后的程序。
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;} }在多次運(yùn)行該程序后,您可能會(huì)得到這樣的信息。
[com.ddlab.rnd.patterns.SingletonType1@3f0ef90c, com.ddlab.rnd.patterns.SingletonType1@2e471e30]但是,使用volatile不能滿足我們的目的。 還是同樣的問(wèn)題,我們可以使用同步方法嗎,是的,我們可以做到。 在許多情況下,大多數(shù)開(kāi)發(fā)人員會(huì)提出與volatile關(guān)鍵字的用法及其在Singleton中的用法有關(guān)的問(wèn)題。 如果經(jīng)驗(yàn)不足的開(kāi)發(fā)人員在其計(jì)算機(jī)上的第一次運(yùn)行中獲得上述類的單個(gè)實(shí)例,則可能會(huì)感到高興。 我有許多開(kāi)發(fā)人員通過(guò)在其計(jì)算機(jī)上運(yùn)行該程序來(lái)證明其合理性,并且他們也向我展示了。 這是正確的,因?yàn)樗麄兒苄疫\(yùn)。 但是我在他們的機(jī)器上多次運(yùn)行了該程序,并告訴他們不要忘記事實(shí)。 現(xiàn)在,他們中的許多人開(kāi)始使用java的great關(guān)鍵字和“同步的”生命保護(hù)程序來(lái)修改程序。 讓我們看看這個(gè)關(guān)鍵字會(huì)發(fā)生什么。
讓我們?cè)谙旅婵吹健?
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;} }但是會(huì)出現(xiàn)性能問(wèn)題。 但是,當(dāng)您對(duì)其進(jìn)行分析時(shí),您將意識(shí)到僅在第一次調(diào)用該方法時(shí)才需要同步。 后續(xù)調(diào)用不需要同步。 因此,不建議在每次調(diào)用時(shí)都使用關(guān)鍵字“ synchronized”。 從長(zhǎng)遠(yuǎn)來(lái)看,它可能會(huì)對(duì)您的產(chǎn)品/項(xiàng)目開(kāi)發(fā)產(chǎn)生不利影響。 為了提高上述程序的效率,請(qǐng)使用以其他方式修改上述程序。 我們不會(huì)同步整個(gè)方法,而是會(huì)做敏感區(qū)域。
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;} }上面的程序看起來(lái)還不錯(cuò),我們很高興,現(xiàn)在讓我們慶祝。 但是,由于存在一個(gè)很大的問(wèn)題,我們?nèi)匀贿h(yuǎn)離艱苦的現(xiàn)實(shí)。 當(dāng)instance為null時(shí),兩個(gè)線程可以同時(shí)進(jìn)入if語(yǔ)句內(nèi)部。 然后,一個(gè)線程進(jìn)入同步塊以初始化實(shí)例,而另一個(gè)則被阻塞。 當(dāng)?shù)谝粋€(gè)線程退出同步塊時(shí),等待線程進(jìn)入并創(chuàng)建另一個(gè)Singleton對(duì)象。 請(qǐng)注意,當(dāng)?shù)诙€(gè)線程進(jìn)入同步塊時(shí),它不會(huì)檢查實(shí)例是否為非空。 讓我們做一個(gè)小的臨床測(cè)試來(lái)面對(duì)現(xiàn)實(shí)。
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);}}現(xiàn)在,您將多次了解上述程序。 接下來(lái)要做什么。
現(xiàn)在,讓我們考慮另一個(gè)被稱為“雙重檢查鎖定”的概念,該概念對(duì)于一組開(kāi)發(fā)人員而言似乎是著名的。 許多開(kāi)發(fā)人員在許多情況下都適用,并認(rèn)為這是最強(qiáng)大的單例形式。
在軟件工程中,雙重檢查鎖定(也稱為“雙重檢查鎖定優(yōu)化”)是一種軟件設(shè)計(jì)模式,用于通過(guò)先測(cè)試鎖定條件(“鎖定提示”)而無(wú)需實(shí)際獲取鎖定來(lái)減少獲取鎖定的開(kāi)銷。鎖。 只有在鎖定
標(biāo)準(zhǔn)檢查表明是否需要鎖定,實(shí)際的鎖定邏輯是否繼續(xù)進(jìn)行。 在大多數(shù)技術(shù)面試中,技術(shù)小組都希望候選人能給出這個(gè)答案。 如果候選人能夠根據(jù)自己的喜好回答此問(wèn)題,技術(shù)小組將很高興并選擇候選人。 如今,它的概念已經(jīng)變得非常重要,但是我要說(shuō)的是技術(shù)小組對(duì)此概念沒(méi)有足夠的經(jīng)驗(yàn)。 讓我們對(duì)其進(jìn)行非常深入的分析。 “雙重檢查鎖定”的基本結(jié)構(gòu)如下。
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處的第二次檢查使不可能創(chuàng)建兩個(gè)不同的Singleton對(duì)象。 好的... 對(duì)于單線程應(yīng)用程序可能是正確的。 細(xì)粒度的多線程應(yīng)用程序呢? 讓我們看下面的順序。
線程1進(jìn)入getInstance()方法。
線程1在// Mark – 1處進(jìn)入同步塊,因?yàn)閷?shí)例為空。
線程1被線程2搶占。
線程2進(jìn)入getInstance()方法。
線程2嘗試獲取// Mark – 1處的鎖,因?yàn)閷?shí)例仍然為空。 但是,由于線程1持有該鎖,因此線程2在// Mark – 1處阻塞。
線程2被線程1搶占。
執(zhí)行線程1,并且由于在// Mark – 2處instance仍然為null,因此創(chuàng)建了Singleton對(duì)象并將其引用分配給實(shí)例。
線程1退出同步塊,并從getInstance()方法返回實(shí)例。
線程1被線程2搶占。
線程2獲取// // Mark – 1處的鎖,并檢查instance是否為null。
由于instance非null,因此不會(huì)創(chuàng)建第二個(gè)Singleton對(duì)象,并且將返回線程1創(chuàng)建的對(duì)象。 雙重檢查鎖定背后的理論是完美的。 不幸的是,現(xiàn)實(shí)是完全不同的。 雙重檢查鎖定的問(wèn)題在于不能保證它可以在單處理器或多處理器計(jì)算機(jī)上工作。 雙重檢查鎖定失敗的問(wèn)題不是由于JVM中的實(shí)現(xiàn)錯(cuò)誤,而是由于當(dāng)前的Java平臺(tái)內(nèi)存模型。 內(nèi)存模型允許所謂的“亂序?qū)懭搿?#xff0c;這是該成語(yǔ)失敗的主要原因。 但是,“亂序?qū)懭搿钡母拍畛隽宋覀兊挠懻摲秶?最重要的是,不應(yīng)以任何形式使用經(jīng)過(guò)仔細(xì)檢查的鎖定,因?yàn)槟荒鼙WC它可以在任何JVM實(shí)現(xiàn)上使用。 如我們所見(jiàn),雖然“雙重檢查鎖定”可能有效,但可能會(huì)意外失敗。 解決辦法是什么 ?
Bill Pugh的解決方案
馬里蘭大學(xué)計(jì)算機(jī)科學(xué)研究員Bill Pugh(摘自Wikipedia)撰寫了有關(guān)用Java實(shí)現(xiàn)Singleton模式的代碼問(wèn)題。 Pugh對(duì)“雙重檢查鎖定”這一習(xí)慣用法的努力導(dǎo)致了Java 5中Java內(nèi)存模型的變化,并導(dǎo)致了通常被視為在Java中實(shí)現(xiàn)Singletons的標(biāo)準(zhǔn)方法。 這種技術(shù)稱為按需初始化持有人慣用語(yǔ),它盡可能懶惰,并且可以在Java的所有已知版本中使用。 它利用了有關(guān)類初始化的語(yǔ)言保證,因此可以在所有Java兼容的編譯器和虛擬機(jī)中正常工作。 嵌套類的引用不早于調(diào)用getInstance()的時(shí)間(因此,類加載器不會(huì)更早地對(duì)其進(jìn)行加載)。 因此,該解決方案是線程安全的,不需要特殊的語(yǔ)言構(gòu)造(即易失性或同步的)。
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;} }上面稱為“ 按需初始化持有人習(xí)慣用法 ”。 Singleton設(shè)計(jì)的上述結(jié)構(gòu)在高度多線程的應(yīng)用程序中非常強(qiáng)大。 讓我們?cè)敿?xì)了解這個(gè)概念。 讓我們考慮一個(gè)下面的小例子。
public class Something {private Something() {}private static class LazyHolder{public static final Something INSTANCE = new Something();}public static Something getInstance() {return LazyHolder.INSTANCE;} }這個(gè)怎么運(yùn)作
該實(shí)現(xiàn)依賴于Java虛擬機(jī)(JVM)中執(zhí)行良好的初始化階段。 有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)Java語(yǔ)言規(guī)范(JLS)的12.4節(jié)。 當(dāng)JVM加載Something類時(shí),該類將進(jìn)行初始化。 由于該類沒(méi)有任何靜態(tài)變量可初始化,因此初始化很容易完成。 在JVM確定必須執(zhí)行LazyHolder之前,不會(huì)初始化其中的靜態(tài)類定義LazyHolder。 靜態(tài)類LazyHolder僅在對(duì)Something類調(diào)用靜態(tài)方法getInstance時(shí)執(zhí)行,并且第一次發(fā)生這種情況時(shí),JVM將加載并初始化LazyHolder類。 LazyHolder類的初始化導(dǎo)致靜態(tài)變量INSTANCE的執(zhí)行是通過(guò)對(duì)外部類Something執(zhí)行(私有)構(gòu)造函數(shù)來(lái)進(jìn)行的。 由于JLS保證類的初始化階段是串行的,即非并發(fā)的,因此在加載和初始化期間,靜態(tài)getInstance方法中不需要進(jìn)一步的同步。 并且由于初始化階段在串行操作中寫入了靜態(tài)變量INSTANCE,因此對(duì)getInstance的所有后續(xù)并發(fā)調(diào)用將返回相同的正確初始化的INSTANCE,而不會(huì)產(chǎn)生任何其他同步開(kāi)銷。
但是,使用“按需初始化持有人慣用語(yǔ)”模式的概念,我們可以實(shí)現(xiàn)線程安全的單例構(gòu)造。 再次出現(xiàn)問(wèn)題,我們可以反思地打破嗎。 是的,我們可以使用我已經(jīng)提到的java反射機(jī)制打破上述概念。 現(xiàn)在問(wèn)題來(lái)了,是否還有其他方法可以構(gòu)建適當(dāng)?shù)膯卫O(shè)計(jì)方法。 是的, Joshua Bloch(Google技術(shù)實(shí)驗(yàn)室首席技術(shù)架構(gòu)師和著名的Book Effective Java的作者)建議使用另一種方法。
package com.ddlab.rnd.patterns; public enum SingletonType3 {INSTANCE;public void doSomething(String arg) {//... perform operation here ...} }這是創(chuàng)建單例類的唯一可靠方法,該類是可序列化的,并且在默認(rèn)情況下是完全線程安全的,而枚舉是完全線程安全的。 關(guān)于反射,使用上面的反射方法,您不能破壞單例對(duì)象,因?yàn)樗鼪](méi)有構(gòu)造函數(shù)。 關(guān)于序列化,您將能夠?qū)ζ溥M(jìn)行序列化,但是每次都會(huì)獲得相同的實(shí)例。 因此,最后我們必須吸收這種創(chuàng)建Singleton設(shè)計(jì)類的現(xiàn)代方法。 但是,許多開(kāi)發(fā)人員對(duì)此一無(wú)所知。
但是,大多數(shù)訪調(diào)員不會(huì)接受以上兩種方法,因?yàn)閷?duì)他們而言,這可能是一個(gè)新概念。 您可以根據(jù)JLS和參考書進(jìn)行論證。 我的大三,同事和朋友每天都抱怨,在面試時(shí)這是他們通常在面試時(shí)面臨的最困難的問(wèn)題。 無(wú)論他們以何種方式回答問(wèn)題,面試官都不會(huì)滿意,這是因?yàn)榇蠖鄶?shù)人不了解單例設(shè)計(jì)課程中的枚舉方法。 如果您也遇到同樣的問(wèn)題,請(qǐng)以約書亞·布洛赫(Joshua Bloch)為例。 您可能會(huì)遇到一些開(kāi)發(fā)人員或訪問(wèn)員的問(wèn)題,即“ Singleton類必須具有私有構(gòu)造函數(shù),并且應(yīng)該有一個(gè)名為getInstance()的方法”。 您必須論證說(shuō)下劃線語(yǔ)句是錯(cuò)誤的,而不是協(xié)議或任何經(jīng)驗(yàn)法則。 這只是我們一段時(shí)間以來(lái)采用的一種方法。 單例背后的主要概念是在任何時(shí)間點(diǎn),都應(yīng)該只有一個(gè)實(shí)例,與如何編寫代碼無(wú)關(guān)。 如果面試不斷地與您爭(zhēng)論,您會(huì)問(wèn)他將枚舉定義為單例方法的問(wèn)題所在。 膽小的面試官可能會(huì)提出一些無(wú)稽之談。 最后,您告訴他,在JDK 5中,枚舉由Josh Bloch和Neal Gafter編寫。 如果開(kāi)發(fā)人員或面試官有膽量,他可以將郵件發(fā)送給這些優(yōu)秀的建筑師。 如果傲慢的面試官仍在作出錯(cuò)誤的論點(diǎn),請(qǐng)教給他一個(gè)教訓(xùn),“先生,您告訴我單身人士的做法,這是無(wú)法打破的。 至少我會(huì)以各種方式破壞您的Singleton設(shè)計(jì)。”
仍然不能使用枚舉破壞上述單例方法,但是我們可以通過(guò)編寫代碼來(lái)創(chuàng)建多個(gè)實(shí)例來(lái)破解上述方法。 下面給出的代碼請(qǐng)勿將以下代碼用于您的商業(yè)產(chǎn)品。 這是打破單例的討厭方法。 讓我們看下面的代碼。
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();}}}但是,上述方法只是一種學(xué)習(xí)技術(shù),而并非在任何地方實(shí)現(xiàn)。 在這個(gè)世界上,每個(gè)對(duì)象都有積極和消極的態(tài)度,但是我們必須遵循積極的態(tài)度才能順利開(kāi)發(fā)我們的產(chǎn)品或項(xiàng)目。
結(jié)論
本文沒(méi)有任何商業(yè)意義。 在本文中,我提供了編寫更好的Singleton設(shè)計(jì)類的更好方法。 可能有最佳方法,如果您知道其他最佳方法或最佳做法,請(qǐng)與我分享。 還提供一些注釋,以便我們可以為更好的編碼標(biāo)準(zhǔn)做出更好的貢獻(xiàn)。 希望您喜歡我的文章。 如有任何錯(cuò)誤,請(qǐng)通過(guò)debadatta.mishra@gmail.com向我報(bào)告。 謝謝。
翻譯自: https://www.javacodegeeks.com/2013/06/singleton-design-pattern-a-lions-eye-view.html
透視變換–鳥(niǎo)瞰圖
總結(jié)
以上是生活随笔為你收集整理的透视变换–鸟瞰图_单例设计模式–鸟瞰的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 怎样手动卸载电脑管家(怎么才能卸载电脑管
- 下一篇: 如何使用Java和XML Config在