這篇文章主要為大家展示了java如何實(shí)現(xiàn)單例模式,內(nèi)容簡而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會有收獲的,下面讓小編帶大家一起來看看吧。
創(chuàng)新互聯(lián)一直秉承“誠信做人,踏實(shí)做事”的原則,不欺瞞客戶,是我們最起碼的底線! 以服務(wù)為基礎(chǔ),以質(zhì)量求生存,以技術(shù)求發(fā)展,成交一個(gè)客戶多一個(gè)朋友!為您提供成都網(wǎng)站建設(shè)、網(wǎng)站制作、成都網(wǎng)頁設(shè)計(jì)、微信小程序開發(fā)、成都網(wǎng)站開發(fā)、成都網(wǎng)站制作、成都軟件開發(fā)、重慶APP軟件開發(fā)是成都本地專業(yè)的網(wǎng)站建設(shè)和網(wǎng)站設(shè)計(jì)公司,等你一起來見證!
1.最基本的單例模式
/** * @author LearnAndGet * @time 2018年11月13日 * 最基本的單例模式 */public class SingletonV1 { private static SingletonV1 instance = new SingletonV1();; //構(gòu)造函數(shù)私有化 private SingletonV1() {} public static SingletonV1 getInstance() { return instance; } }
import org.junit.Test;public class SingletonTest { @Test public void test01() throws Exception { SingletonV1 s1 = SingletonV1.getInstance(); SingletonV1 s2 = SingletonV1.getInstance(); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); } }//運(yùn)行結(jié)果如下:589873731 589873731
2.類加載時(shí)不初始化實(shí)例的模式
上述單例模式在類加載的時(shí)候,就會生成實(shí)例,可能造成空間浪費(fèi),如果需要修改成,在需要使用時(shí)才生成實(shí)例,則可修改代碼如下:
public class SingletonV2 { private static SingletonV2 instance; //構(gòu)造函數(shù)私有化 private SingletonV2() {} public static SingletonV2 getInstance() { if(instance == null) { instance = new SingletonV2(); } return instance; } }
然而,上述方案雖然在類加載時(shí)不會生成實(shí)例,但是存在線程安全問題,如果線程A在執(zhí)行到第10行時(shí),線程B也進(jìn)入該代碼塊,恰好也執(zhí)行好第10行,此時(shí)如果實(shí)例尚未生成,則線程A和線程B都會執(zhí)行第12行的代碼,各自生成一個(gè)實(shí)例,此時(shí)就違背了單例模式的設(shè)計(jì)原則。實(shí)際測試代碼如下:
public class SingletonTest { @Test public void test02() throws Exception { for(int i=0;i<1000;i++) { Thread th2 = new getInstanceThread(); th2.start(); } } class getInstanceThread extends Thread { public void run() { try { SingletonV2 s = SingletonV2.getInstance(); System.out.println(Thread.currentThread().getName()+" get Instance "+s.hashCode()+" Time: "+System.currentTimeMillis()); }catch(Exception e) { e.printStackTrace(); } } } }
經(jīng)過多次測試,可能產(chǎn)生如下輸出結(jié)果:
3.線程安全的單例模式
在上述單例模式下進(jìn)行改進(jìn),在getInstance方法前加入 Sychronized關(guān)鍵字,來實(shí)現(xiàn)線程安全,修改后代碼如下:
public class SingletonV3 { private static SingletonV3 instance; //構(gòu)造函數(shù)私有化 private SingletonV3() {} //synchronized關(guān)鍵字在靜態(tài)方法上,鎖定的是當(dāng)前類: public static synchronized SingletonV3 getInstance() { if(instance == null) { instance = new SingletonV3(); } return instance; } }
增加sychronized關(guān)鍵字后,確實(shí)能夠改善線程安全問題,但是也帶來了額外的鎖開銷。性能受到一定影響。舉例來說,此時(shí)如果有1000個(gè)線程都需要使用SingletonV3實(shí)例,因?yàn)榧渔i的位置在getInstance上,因此,每個(gè)線程都必須等待其他獲取了鎖的線程完全執(zhí)行完鎖中的方法后,才能夠進(jìn)入該方法并獲取自己的實(shí)例。
4.雙重校檢+線程安全單例模式
于是可以在上述代碼的基礎(chǔ)上,只有當(dāng)Singleton實(shí)例未被初始化時(shí),對實(shí)例化方法加鎖即可。在Singleton實(shí)例已經(jīng)被初始化時(shí),無需加鎖,直接返回當(dāng)前Singleton對象。代碼如下:
private static SingletonV4 instance; //構(gòu)造函數(shù)私有化 private SingletonV4() {} public static SingletonV4 getInstance() { if(instance == null) { synchronized(SingletonV4.class) { //雙重校檢 if(instance == null) { instance = new SingletonV4(); } } } return instance; }
5.內(nèi)部類單例模式
盡管上述方案解決了同步問題,雙重校檢也使得性能開銷大大減小,但是,只有有synchronized關(guān)鍵字的存在。性能多多少少還是會有一些影響,此時(shí),我們想到了 "內(nèi)部類"的用法。
①.內(nèi)部類不會隨著類的加載而加載
②.一個(gè)類被加載,當(dāng)且僅當(dāng)其某個(gè)靜態(tài)成員(靜態(tài)域、構(gòu)造器、靜態(tài)方法等)被調(diào)用時(shí)發(fā)生。
靜態(tài)內(nèi)部類隨著方法調(diào)用而被加載,只加載一次,不存在并發(fā)問題,所以是線程安全?;诖耍薷拇a如下:
public class SingletonV5 { //構(gòu)造函數(shù)私有化 private SingletonV5() {} static class SingetonGet { private static final SingletonV5 instance = new SingletonV5(); } public static SingletonV5 getInstance() { return SingetonGet.instance; } }
6.反射都不能破壞的單例模式
靜態(tài)內(nèi)部類實(shí)現(xiàn)的單例模式,是目前比較推薦的方式,但是在java功能強(qiáng)大反射的機(jī)制下,它就是個(gè)弟弟,此時(shí)利用反射仍然能夠創(chuàng)建出多個(gè)實(shí)例,以下是創(chuàng)建實(shí)例的代碼:
@Test public void test4() { //普通方式獲取實(shí)例s1,s2 SingletonV5 s1 = SingletonV5.getInstance(); SingletonV5 s2 = SingletonV5.getInstance(); //利用反射獲取實(shí)例s3,s4 SingletonV5 s3 = null; SingletonV5 s4 = null; try { Class<SingletonV5> clazz = SingletonV5.class; Constructor<SingletonV5> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); s3 = constructor.newInstance(); s4 = constructor.newInstance(); }catch(Exception e) { e.printStackTrace(); } System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println(s3.hashCode()); System.out.println(s4.hashCode()); }
輸出結(jié)果如下:
589873731
589873731
200006406
2052001577
可以看到,s1和s2擁有相同的哈希碼,因此他們是同一個(gè)實(shí)例,但是s3、s4,是通過反射后用構(gòu)造函數(shù)重新構(gòu)造生成的實(shí)例,他們均與s1,s2不同。此時(shí)單例模式下產(chǎn)生了多個(gè)不同的對象,違反了設(shè)計(jì)原則。
基于上述反射可能造成的單例模式失效,考慮在私有的構(gòu)造函數(shù)中添加是否初始化的標(biāo)記位,使私有構(gòu)造方法只可能被執(zhí)行一次。
public class SingletonV6 { //是否已經(jīng)初始化過的標(biāo)記位 private static boolean isInitialized = false; //構(gòu)造函數(shù)中,當(dāng)實(shí)例已經(jīng)被初始化時(shí),不能繼續(xù)獲取新實(shí)例 private SingletonV6() { synchronized(SingletonV6.class) { if(isInitialized == false) { isInitialized = !isInitialized; }else { throw new RuntimeException("單例模式被破壞..."); } } } static class SingetonGet { private static final SingletonV6 instance = new SingletonV6(); } public static SingletonV6 getInstance() { return SingetonGet.instance; } }
測試代碼如下:
@Test public void test5() { SingletonV6 s1 = SingletonV6.getInstance(); SingletonV6 s2 = null; try { Class<SingletonV6> clazz = SingletonV6.class; Constructor<SingletonV6> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); s2 = constructor.newInstance(); }catch(Exception e) { e.printStackTrace(); } System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); }
運(yùn)行上述代碼時(shí),會拋出異常:
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at SingletonTest.SingletonTest.test5(SingletonTest.java:98) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206) Caused by: java.lang.RuntimeException: 單例模式被破壞... at SingletonTest.SingletonV6.<init>(SingletonV6.java:26) ... 28 more2052001577
7.序列化反序列化都不能破壞的單例模式
經(jīng)過上述改進(jìn),反射也不能夠破壞單例模式了。但是,依然存在一種可能造成上述單例模式產(chǎn)生兩個(gè)不同的實(shí)例,那就是序列化。當(dāng)一個(gè)對象A經(jīng)過序列化,然后再反序列化,獲取到的對象B和A是否是同一個(gè)實(shí)例呢,驗(yàn)證代碼如下:
/** * @Author {LearnAndGet} * @Time 2018年11月13日 * @Discription:測試序列化并反序列化是否還是同一對象 */package SingletonTest;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub SingletonV6 s1 = SingletonV6.getInstance(); ObjectOutput objOut = null; try { //將s1序列化(記得將Singleton實(shí)現(xiàn)Serializable接口) objOut = new ObjectOutputStream(new FileOutputStream("c:\\a.objFile")); objOut.writeObject(s1); objOut.close(); //反序列化得到s2 ObjectInput objIn = new ObjectInputStream(new FileInputStream("c:\\a.objFile")); SingletonV6 s2 = (SingletonV6) objIn.readObject(); objIn.close(); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
輸出結(jié)果如下:
1118140819
990368553
可見,此時(shí)序列化前的對象s1和經(jīng)過序列化->反序列化步驟后的到的對象s2,并不是同一個(gè)對象,因此,出現(xiàn)了兩個(gè)實(shí)例,再次違背了單例模式的設(shè)計(jì)原則。
為了消除問題,在單例模式類中,實(shí)現(xiàn)Serializable接口之后 添加對readResolve()方法的實(shí)現(xiàn):當(dāng)從I/O流中讀取對象時(shí),readResolve()方法都會被調(diào)用到。實(shí)際上就是用readResolve()中返回的對象直接替換在反序列化過程中創(chuàng)建的對象,而被創(chuàng)建的對象則會被垃圾回收掉。這就確保了在序列化和反序列化的過程中沒人可以創(chuàng)建新的實(shí)例,修改后的代碼如下:
package SingletonTest;import java.io.Serializable;/** * @author LearnAndGet * * @time 2018年11月13日 * */public class SingletonV6 implements Serializable{ //是否已經(jīng)初始化過的標(biāo)記位 private static boolean isInitialized = false; //構(gòu)造函數(shù)中,當(dāng)實(shí)例已經(jīng)被初始化時(shí),不能繼續(xù)獲取新實(shí)例 private SingletonV6() { synchronized(SingletonV6.class) { if(isInitialized == false) { isInitialized = !isInitialized; }else { throw new RuntimeException("單例模式被破壞..."); } } } static class SingetonGet { private static final SingletonV6 instance = new SingletonV6(); } public static SingletonV6 getInstance() { return SingetonGet.instance; } //實(shí)現(xiàn)readResolve方法 private Object readResolve() { return getInstance(); } }
重新運(yùn)行上述序列化和反序列過程,可以發(fā)現(xiàn),此時(shí)得到的對象是同一對象。
1118140819
1118140819
8.總結(jié)
在實(shí)際開發(fā)中,根據(jù)自己的需要,選擇對應(yīng)的單例模式即可,不一樣非要實(shí)現(xiàn)第7節(jié)中那種無堅(jiān)不摧的單例模式。畢竟不是所有場景下都需要實(shí)現(xiàn)序列化接口, 也并不是所有人都會用反射來破壞單例模式。因此比較常用的是第5節(jié)中的,內(nèi)部類單例模式,代碼簡潔明了,且節(jié)省空間。
以上就是關(guān)于java如何實(shí)現(xiàn)單例模式的內(nèi)容,如果你們有學(xué)習(xí)到知識或者技能,可以把它分享出去讓更多的人看到。
網(wǎng)站題目:java如何實(shí)現(xiàn)單例模式
轉(zhuǎn)載源于:http://aaarwkj.com/article0/peisoo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、網(wǎng)站維護(hù)、搜索引擎優(yōu)化、網(wǎng)頁設(shè)計(jì)公司、移動網(wǎng)站建設(shè)、商城網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)