摘要:?jiǎn)卫J绞且环N常用的設(shè)計(jì)模式也可能是設(shè)計(jì)模式中代碼量最少的設(shè)計(jì)模式。簡(jiǎn)介單例模式屬于中設(shè)計(jì)模式中的創(chuàng)建型模式定義是確保某一個(gè)類(lèi)只有一個(gè)實(shí)例并提供一個(gè)全局的訪問(wèn)點(diǎn)。
單例模式是一種常用的設(shè)計(jì)模式、也可能是設(shè)計(jì)模式中代碼量最少的設(shè)計(jì)模式。但是少并不意味著簡(jiǎn)單、想要用好、用對(duì)單例、就的費(fèi)一番腦子了。因?yàn)樗锩嫔婕暗搅撕芏郕ava底層的知識(shí)如類(lèi)裝載機(jī)制、Java內(nèi)存模型、volatile等知識(shí)點(diǎn)。
簡(jiǎn)介單例模式屬于23中設(shè)計(jì)模式中的創(chuàng)建型模式、定義是確保某一個(gè)類(lèi)只有一個(gè)實(shí)例、并提供一個(gè)全局的訪問(wèn)點(diǎn)。
具有以下3個(gè)特性:
只能有一個(gè)實(shí)例
必須自己創(chuàng)建自己唯一實(shí)例
提供全局訪問(wèn)點(diǎn)
基本實(shí)現(xiàn)思路單例要求類(lèi)只能返回同一對(duì)象的引用、必須提供一個(gè)靜態(tài)獲取該實(shí)例的方法
實(shí)現(xiàn)可以通過(guò)以下兩步:
私有化構(gòu)造方法、防止外部實(shí)例化、只有通過(guò)對(duì)外提供的靜態(tài)方法來(lái)獲取唯一實(shí)例
提供一個(gè)靜態(tài)方法獲取對(duì)象的實(shí)例。
單例的7種實(shí)現(xiàn)方式public class EagetSingleton { private static final EagetSingleton INSANCE = new EagetSingleton(); // 私有化構(gòu)造函數(shù)、防止外部實(shí)例化 private EagetSingleton() { } // 提供靜態(tài)外部訪問(wèn)方法 public static EagetSingleton getInstance() { return INSANCE; } }
優(yōu)點(diǎn):寫(xiě)法簡(jiǎn)單、類(lèi)裝載時(shí)就實(shí)例化了靜態(tài)變量、避免了線程并發(fā)問(wèn)題。
缺點(diǎn):在類(lèi)裝載過(guò)程中就實(shí)例化了對(duì)象、造成了資源浪費(fèi)。
public class StaticBlockSingleton { private static StaticBlockSingleton INSTANCE = null; static { try { INSTANCE = new StaticBlockSingleton(); } catch (Exception e) { } } // 私有化構(gòu)造函數(shù)、防止外部實(shí)例化 private StaticBlockSingleton() { } // 提供靜態(tài)外部訪問(wèn)方法 public static StaticBlockSingleton getInstance() { return INSTANCE; } }
這種方式和上述實(shí)現(xiàn)方式基本相同、只是把類(lèi)實(shí)例化的過(guò)程放到了靜態(tài)代碼塊中來(lái)實(shí)例化、同樣也是在類(lèi)裝載過(guò)程執(zhí)行靜態(tài)代碼塊、優(yōu)缺點(diǎn)基本相同但是它可以在類(lèi)實(shí)例化過(guò)程中做一些額外的操作如異常處理等。
public class LazySingleton { private static LazySingleton INSTANCE = null; // 私有化構(gòu)造函數(shù)、防止外部實(shí)例化 private LazySingleton() { } // 提供靜態(tài)外部訪問(wèn)方法 public static LazySingleton getInstance() { if (null == INSTANCE) { -------- 1 INSTANCE = new LazySingleton(); ------2 } return INSTANCE; } }
優(yōu)點(diǎn):實(shí)現(xiàn)了懶加載、避免了資源的浪費(fèi)。
缺點(diǎn):線程不安全、在多線程情況下當(dāng)一個(gè)線程執(zhí)行到 1 處的時(shí)候、還沒(méi)有來(lái)得及往下執(zhí)行另一個(gè)線程也到 1 處 這樣兩個(gè)線程同時(shí)執(zhí)行 2 處代碼、破壞了單例。
public class LazySyncSingleton { private static LazySyncSingleton INSTANCE = null; // 私有化構(gòu)造函數(shù)、防止外部實(shí)例化 private LazySyncSingleton() { } // 效率低下 // 提供靜態(tài)外部訪問(wèn)方法 public static synchronized LazySyncSingleton getInstance() { if (null == INSTANCE) { INSTANCE = new LazySyncSingleton(); } return INSTANCE; } }
解決了3中線程不安全的問(wèn)題、利用synchronized對(duì)getInstance()方法加鎖以達(dá)到同步訪問(wèn)。
優(yōu)點(diǎn):線程同步
缺點(diǎn):效率低下、此方式對(duì)整個(gè)對(duì)象加鎖、每次訪問(wèn)getInstance() 都需要同步訪問(wèn)、這種情況多線程并發(fā)效率非常低下、其實(shí)我們只需要在對(duì)象還沒(méi)實(shí)例化前加鎖就可以了、實(shí)例化后就不存在并發(fā)問(wèn)題了。
public class DCheckSingleton { private static volatile DCheckSingleton INSTANCE = null; // 私有化構(gòu)造函數(shù)、防止外部實(shí)例化 private DCheckSingleton() { } // 提供靜態(tài)外部訪問(wèn)方法 public static DCheckSingleton getInstance() { if (null == INSTANCE) { synchronized (DCheckSingleton.class) { if (null == INSTANCE) { INSTANCE = new DCheckSingleton(); } } } return INSTANCE; } }
解決了4中并發(fā)情況下效率低下的問(wèn)題。
優(yōu)點(diǎn):線程安全、延遲加載、效率高
涉及到知識(shí)點(diǎn): 1:volatile 關(guān)鍵字 確保內(nèi)存的可見(jiàn)性和有序性。如果不加volatile關(guān)鍵字會(huì)有什么情況? 我知道在對(duì)象實(shí)例化時(shí)INSTANCE = new DCheckSingleton();這一句代碼JVM中并不是一步執(zhí)行的而是分為三步(1)在棧內(nèi)存中為 創(chuàng)建對(duì)象的引用指針 INSTANCE (2)在堆內(nèi)存中開(kāi)辟一塊空間來(lái)存放實(shí)例化的對(duì)象 new DCheckSingleton(); (3)將INSTANCE指向堆內(nèi)存空間地址J、VM只保證了代碼執(zhí)行結(jié)果的正確性、并不保證執(zhí)行順序(這里涉及到Java內(nèi)存模型知識(shí)點(diǎn)在這就不多說(shuō)了、感興趣的同學(xué)可以去了解下JVM一些底層實(shí)現(xiàn)原理)所以 1 ,2,3三步也可能是1 ,3 ,2 這樣我們就可能拿到的時(shí)一個(gè)半成品的對(duì)象了。
2: 涉及到類(lèi)實(shí)例化知識(shí)點(diǎn)
3: 涉及到Java內(nèi)存模型
4:涉及到JVM的一些執(zhí)行優(yōu)化、指令重排等
public class InnerSingleton { private InnerSingleton() { } public static InnerSingleton getInstance() { return InnerClassSingleton.INSTANCE; } private static class InnerClassSingleton{ private static final InnerSingleton INSTANCE = new InnerSingleton(); } }
這種方式和餓漢式的實(shí)現(xiàn)機(jī)制基本相同、都是利用了類(lèi)裝載機(jī)制來(lái)保證線程的安全、它和餓漢式的唯一區(qū)別就是實(shí)現(xiàn)了懶加載的機(jī)制、只有在調(diào)用getInstance()方法時(shí)才去進(jìn)行InnerClassSingleton類(lèi)的實(shí)例化。
優(yōu)點(diǎn):避免了線程不安全,延遲加載,效率高。
public enum EnumsSingleton { INSTANCE; @SuppressWarnings("unused") private void method() { System.out.println("------- newInstance"); } }
借助JDK1.5中添加的枚舉來(lái)實(shí)現(xiàn)單例模式。不僅能避免多線程同步問(wèn)題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象??赡苁且?yàn)槊杜e在JDK1.5中才添加、所以在實(shí)際項(xiàng)目開(kāi)發(fā)中、很少見(jiàn)人這么寫(xiě)過(guò)。
到這單例幾種實(shí)現(xiàn)方式以及每種方式的優(yōu)缺點(diǎn)都做了一些簡(jiǎn)單的介紹、枚舉雖小但是設(shè)計(jì)的知識(shí)點(diǎn)很多。
優(yōu)點(diǎn)在單例模式中,活動(dòng)的單例只有一個(gè)實(shí)例,對(duì)單例類(lèi)的所有實(shí)例化得到的都是相同的一個(gè)實(shí)例。這樣就 防止其它對(duì)象對(duì)自己的實(shí)例化,確保所有的對(duì)象都訪問(wèn)一個(gè)實(shí)例
單例模式具有一定的伸縮性,類(lèi)自己來(lái)控制實(shí)例化進(jìn)程,類(lèi)就在改變實(shí)例化進(jìn)程上有相應(yīng)的伸縮性。
提供了對(duì)唯一實(shí)例的受控訪問(wèn)。
由于在系統(tǒng)內(nèi)存中只存在一個(gè)對(duì)象,因此可以 節(jié)約系統(tǒng)資源,當(dāng) 需要頻繁創(chuàng)建和銷(xiāo)毀的對(duì)象時(shí)單例模式無(wú)疑可以提高系統(tǒng)的性能。
允許可變數(shù)目的實(shí)例。
避免對(duì)共享資源的多重占用
缺點(diǎn)不適用于變化的對(duì)象,如果同一類(lèi)型的對(duì)象總是要在不同的用例場(chǎng)景發(fā) 生變化,單例就會(huì)引起數(shù)據(jù)的錯(cuò)誤,不能保存彼此的狀態(tài)。
由于單利模式中沒(méi)有抽象層,因此單例類(lèi)的擴(kuò)展有很大的困難。
單例類(lèi)的職責(zé)過(guò)重,在一定程度上違背了“單一職責(zé)原則”。
濫用單例將帶來(lái)一些負(fù)面問(wèn)題,如為了節(jié)省資源將數(shù)據(jù)庫(kù)連接池對(duì)象設(shè) 計(jì)為的單例類(lèi),可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過(guò)多而出現(xiàn)連接池溢 出;如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為是垃圾而被回收, 這將導(dǎo)致對(duì)象狀態(tài)的丟失。
使用場(chǎng)景需要頻繁的進(jìn)行創(chuàng)建和銷(xiāo)毀的對(duì)象;
創(chuàng)建對(duì)象時(shí)耗時(shí)過(guò)多或耗費(fèi)資源過(guò)多,但又經(jīng)常用到的對(duì)象;
工具類(lèi)對(duì)象;
頻繁訪問(wèn)數(shù)據(jù)庫(kù)或文件的對(duì)象。
注意最后在簡(jiǎn)單聊一下如何防止暴力破壞單例。主要介紹兩種方式以及如何來(lái)防范這兩種方式。
1: 利用Java的反射方式
EagerSingleton instance = EagerSingleton.getInstance(); Constructor instance2 = instance.getClass().getDeclaredConstructor(); instance2.setAccessible(true); EagerSingleton instance3 = (EagerSingleton) instance2.newInstance(); System.out.println("===" + instance); System.out.println("===" + instance3);
利用Java的反射方式可以達(dá)到爆力破解單例的效果、運(yùn)行結(jié)果我就不在這貼出了有興趣的可以自己試試instance 和 instance3 肯定不是一個(gè)對(duì)象。
如何來(lái)防范這方式? 其實(shí)也很簡(jiǎn)單Java Security 中為我們提供了現(xiàn)成的方法。只需要在私有構(gòu)造中使用SecurityManager 進(jìn)行檢查下就可以代碼如下。
// 私有的構(gòu)造方法,防止外部實(shí)例化 private EagerSingleton() { SecurityManager sm = new SecurityManager(); sm.checkPermission(new ReflectPermission("禁止反射")); }
2: 第二種方式是利用Java 序列化和反序列化來(lái)實(shí)現(xiàn)
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt")); out.writeObject(instance); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt")); EagerSingleton readObject = (EagerSingleton) in.readObject(); in.close(); System.out.println("==" + instance); System.out.println("==" + readObject);
如何防范? 很簡(jiǎn)單只需要重寫(xiě)readResolve() 反方就可以了
private Object readResolve() { return EagerSingleton.instance; }
兩種暴力破解和防范的方式都介紹完了,感興趣的同志可以去試試我這里沒(méi)有貼出完整的測(cè)試代碼和運(yùn)行結(jié)果。
~~~~~~到這我們的小單例已經(jīng)介紹完了,有沒(méi)有感到驚訝?。?!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74244.html
摘要:老實(shí)說(shuō),當(dāng)時(shí)一進(jìn)入世界的大門(mén)就暈了,各種規(guī)范概念和英文縮寫(xiě)詞能把人整的暈暈乎乎。等新的英文縮寫(xiě)又出現(xiàn)了,一口老血還沒(méi)來(lái)得及噴出,又重新振作開(kāi)始新的學(xué)習(xí)征程。 showImg(http://upload-images.jianshu.io/upload_images/1131767-1c5d16e39435df10.jpg?imageMogr2/auto-orient/strip%7Ci...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報(bào)率高。馬上就十一國(guó)慶假期了,給小伙伴們分享下,從小白程序員到大廠高級(jí)技術(shù)專(zhuān)家我看過(guò)哪些技術(shù)類(lèi)書(shū)籍。 大家好,我是...
摘要:懶漢式線程安全在進(jìn)行類(lèi)初始化的時(shí)候就實(shí)例化了餓漢式線程不安全的單列模式餓漢式線程安全的單列模式但是效率不佳因?yàn)樗械木€程都需要同步等待獲取單例對(duì)象餓漢式線程安全的單列模式效率由于上面一種餓漢的方式因?yàn)樗械木€程都需要同步等待獲取單例對(duì)象靜態(tài) /** 懶漢式* 線程安全,在進(jìn)行類(lèi)初始化的時(shí)候就實(shí)例化了* */ class Instance0 { private static ...
摘要:前言單例模式是設(shè)計(jì)模式中最簡(jiǎn)單最容易理解的一種,維基百科的定義如下單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。 前言 單例模式是設(shè)計(jì)模式(Design Pattern)中最簡(jiǎn)單、最容易理解的一種,維基百科[1]的定義如下: 單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類(lèi) 類(lèi) (計(jì)算機(jī)科學(xué)))必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一...
閱讀 3250·2021-11-15 11:37
閱讀 2464·2021-09-29 09:48
閱讀 3828·2021-09-22 15:55
閱讀 3025·2021-09-22 10:02
閱讀 2649·2021-08-25 09:40
閱讀 3240·2021-08-03 14:03
閱讀 1708·2019-08-29 13:11
閱讀 1581·2019-08-29 12:49