Java單例模式的深入了解
一、設(shè)計(jì)模式概覽
1.1、軟件設(shè)計(jì)模式的概念
軟件設(shè)計(jì)模式(Software Design Pattern),又稱設(shè)計(jì)模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。它描述了在軟件設(shè)計(jì)過程中的一些不斷重復(fù)發(fā)生的問題,以及該問題的解決方案。也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),具有一定的普遍性,可以反復(fù)使用。其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。
1.2、軟件設(shè)計(jì)模式的基本要素
1. 模式名稱
每一個(gè)模式都有自己的名字,通常用一兩個(gè)詞來描述,可以根據(jù)模式的問題、特點(diǎn)、解決方案、功能和效果來命名。模式名稱(PatternName)有助于我們理解和記憶該模式,也方便我們來討論自己的設(shè)計(jì)。
2. 問題
問題(Problem)描述了該模式的應(yīng)用環(huán)境,即何時(shí)使用該模式。它解釋了設(shè)計(jì)問題和問題存在的前因后果,以及必須滿足的一系列先決條件。
3. 解決方案
模式問題的解決方案(Solution)包括設(shè)計(jì)的組成成分、它們之間的相互關(guān)系及各自的職責(zé)和協(xié)作方式。因?yàn)槟J骄拖褚粋€(gè)模板,可應(yīng)用于多種不同場(chǎng)合,所以解決方案并不描述一個(gè)特定而具體的設(shè)計(jì)或?qū)崿F(xiàn),而是提供設(shè)計(jì)問題的抽象描述和怎樣用一個(gè)具有一般意義的元素組合(類或?qū)ο蟮?組合)來解決這個(gè)問題。
4. 效果
描述了模式的應(yīng)用效果以及使用該模式應(yīng)該權(quán)衡的問題,即模式的優(yōu)缺點(diǎn)。主要是對(duì)時(shí)間和空間的衡量,以及該模式對(duì)系統(tǒng)的靈活性、擴(kuò)充性、可移植性的影響,也考慮其實(shí)現(xiàn)問題。顯式地列出這些效果(Consequence)對(duì)理解和評(píng)價(jià)這些模式有很大的幫助。
1.3、GoF 的 23 種設(shè)計(jì)模式的分類和功能
1. 根據(jù)目的來分
根據(jù)模式是用來完成什么工作來劃分,
創(chuàng)建型模式:用于描述“怎樣創(chuàng)建對(duì)象”,它的主要特點(diǎn)是“將對(duì)象的創(chuàng)建與使用分離”。GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創(chuàng)建型模式。
結(jié)構(gòu)型模式:用于描述如何將類或?qū)ο蟀茨撤N布局組成更大的結(jié)構(gòu),GoF 中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等 7 種結(jié)構(gòu)型模式。
行為型模式:用于描述類或?qū)ο笾g怎樣相互協(xié)作共同完成單個(gè)對(duì)象都無法單獨(dú)完成的任務(wù),以及怎樣分配職責(zé)。GoF 中提供了模板方法、策略、命令、職責(zé)鏈、狀態(tài)、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器等 11 種行為型模式。
2. GoF的23種設(shè)計(jì)模式的功能
單例(Singleton)模式:某個(gè)類只能生成一個(gè)實(shí)例,該類提供了一個(gè)全局訪問點(diǎn)供外部獲取該實(shí)例,其拓展是有限多例模式。
原型(Prototype)模式:將一個(gè)對(duì)象作為原型,通過對(duì)其進(jìn)行復(fù)制而克隆出多個(gè)和原型類似的新實(shí)例。
工廠方法(Factory Method)模式:定義一個(gè)用于創(chuàng)建產(chǎn)品的接口,由子類決定生產(chǎn)什么產(chǎn)品。
抽象工廠(AbstractFactory)模式:提供一個(gè)創(chuàng)建產(chǎn)品族的接口,其每個(gè)子類可以生產(chǎn)一系列相關(guān)的產(chǎn)品。
建造者(Builder)模式:將一個(gè)復(fù)雜對(duì)象分解成多個(gè)相對(duì)簡(jiǎn)單的部分,然后根據(jù)不同需要分別創(chuàng)建它們,最后構(gòu)建成該復(fù)雜對(duì)象。
代理(Proxy)模式:為某對(duì)象提供一種代理以控制對(duì)該對(duì)象的訪問。即客戶端通過代理間接地訪問該對(duì)象,從而限制、增強(qiáng)或修改該對(duì)象的一些特性。
適配器(Adapter)模式:將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作。
橋接(Bridge)模式:將抽象與實(shí)現(xiàn)分離,使它們可以獨(dú)立變化。它是用組合關(guān)系代替繼承關(guān)系來實(shí)現(xiàn),從而降低了抽象和實(shí)現(xiàn)這兩個(gè)可變維度的耦合度。
裝飾(Decorator)模式:動(dòng)態(tài)的給對(duì)象增加一些職責(zé),即增加其額外的功能。
外觀(Facade)模式:為多個(gè)復(fù)雜的子系統(tǒng)提供一個(gè)一致的接口,使這些子系統(tǒng)更加容易被訪問。
享元(Flyweight)模式:運(yùn)用共享技術(shù)來有效地支持大量細(xì)粒度對(duì)象的復(fù)用。
組合(Composite)模式:將對(duì)象組合成樹狀層次結(jié)構(gòu),使用戶對(duì)單個(gè)對(duì)象和組合對(duì)象具有一致的訪問性。
模板方法(TemplateMethod)模式:定義一個(gè)操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變?cè)撍惴ńY(jié)構(gòu)的情況下重定義該算法的某些特定步驟。
策略(Strategy)模式:定義了一系列算法,并將每個(gè)算法封裝起來,使它們可以相互替換,且算法的改變不會(huì)影響使用算法的客戶。
命令(Command)模式:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,使發(fā)出請(qǐng)求的責(zé)任和執(zhí)行請(qǐng)求的責(zé)任分割開。
職責(zé)鏈(Chain of Responsibility)模式:把請(qǐng)求從鏈中的一個(gè)對(duì)象傳到下一個(gè)對(duì)象,直到請(qǐng)求被響應(yīng)為止。通過這種方式去除對(duì)象之間的耦合。
狀態(tài)(State)模式:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)發(fā)生改變時(shí)改變其行為能力。
觀察者(Observer)模式:多個(gè)對(duì)象間存在一對(duì)多關(guān)系,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),把這種改變通知給其他多個(gè)對(duì)象,從而影響其他對(duì)象的行為。
中介者(Mediator)模式:定義一個(gè)中介對(duì)象來簡(jiǎn)化原有對(duì)象之間的交互關(guān)系,降低系統(tǒng)中對(duì)象間的耦合度,使原有對(duì)象之間不必相互了解。
迭代器(Iterator)模式:提供一種方法來順序訪問聚合對(duì)象中的一系列數(shù)據(jù),而不暴露聚合對(duì)象的內(nèi)部表示。
訪問者(Visitor)模式:在不改變集合元素的前提下,為一個(gè)集合中的每個(gè)元素提供多種訪問方式,即每個(gè)元素有多個(gè)訪問者對(duì)象訪問。
備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取并保存一個(gè)對(duì)象的內(nèi)部狀態(tài),以便以后恢復(fù)它。
解釋器(Interpreter)模式:提供如何定義語(yǔ)言的文法,以及對(duì)語(yǔ)言句子的解釋方法,即解釋器。
1.4、軟件設(shè)計(jì)的七大原則
原則 | 描述 | 作用 |
---|---|---|
開閉原則 | 對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉 | 降低維護(hù)帶來的新風(fēng)險(xiǎn) |
依賴倒置原則 | 高層不應(yīng)該依賴低層,要面向接口編程 | 更利于代碼結(jié)構(gòu)的升級(jí)擴(kuò)展 |
單一職責(zé)原則 | 一個(gè)類只干一件事,實(shí)現(xiàn)類要單一 | 便于理解,提高代碼的可讀性 |
接口隔離原則 | 一個(gè)接口只干一件事,接口要精簡(jiǎn)單一 | 功能解耦,高聚合、低耦合 |
迪米特法則 | 不該知道的不要知道,一個(gè)類應(yīng)該保持對(duì)其它對(duì)象最少的了解,降低耦合度 | 只和朋友交流,不和陌生人說話,減少代碼臃腫 |
里氏替換原則 | 不要破壞繼承體系,子類重寫方法功能發(fā)生改變,不應(yīng)該影響父類方法的含義 | 防止繼承泛濫 |
合成復(fù)用原則 | 盡量使用組合或者聚合關(guān)系實(shí)現(xiàn)代碼復(fù)用,少使用繼承 | 降低代碼耦合 |
實(shí)際上,這些原則的目的只有一個(gè):降低對(duì)象之間的耦合,增加程序的可復(fù)用性、可擴(kuò)展性和可維護(hù)性。
記憶口訣:訪問加限制,函數(shù)要節(jié)儉,依賴不允許,動(dòng)態(tài)加接口,父類要抽象,擴(kuò)展不更改。
在程序設(shè)計(jì)時(shí),我們應(yīng)該將程序功能最小化,每個(gè)類只干一件事。若有類似功能基礎(chǔ)之上添加新功能,則要合理使用繼承。對(duì)于多方法的調(diào)用,要會(huì)運(yùn)用接口,同時(shí)合理設(shè)置接口功能與數(shù)量。最后類與類之間做到低耦合高內(nèi)聚。
二、單利模式
1.1、單例模式的相關(guān)定義
指一個(gè)類只有一個(gè)實(shí)例,且該類能自行創(chuàng)建這個(gè)實(shí)例的一種模式。例如,Windows 中只能打開一個(gè)任務(wù)管理器,這樣可以避免因打開多個(gè)任務(wù)管理器窗口而造成內(nèi)存資源的浪費(fèi),或出現(xiàn)各個(gè)窗口顯示內(nèi)容的不一致等錯(cuò)誤。
單利模式三個(gè)特點(diǎn)
單例類只有一個(gè)實(shí)例對(duì)象;
該單例對(duì)象必須由單例類自行創(chuàng)建;
單例類對(duì)外提供一個(gè)訪問該單例的全局訪問點(diǎn)。
單例模式的優(yōu)點(diǎn)
單例模式可以保證內(nèi)存里只有一個(gè)實(shí)例,減少了內(nèi)存的開銷。
可以避免對(duì)資源的多重占用。
單例模式設(shè)置全局訪問點(diǎn),可以優(yōu)化和共享資源的訪問。
單例模式的缺點(diǎn):
單例模式一般沒有接口,擴(kuò)展困難。如果要擴(kuò)展,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。
在并發(fā)測(cè)試中,單例模式不利于代碼調(diào)試。在調(diào)試過程中,如果單例中的代碼沒有執(zhí)行完,也不能模擬生成一個(gè)新的對(duì)象。
單例模式的功能代碼通常寫在一個(gè)類中,如果功能設(shè)計(jì)不合理,則很容易違背單一職責(zé)原則。
單例模式的應(yīng)用場(chǎng)景
需要頻繁創(chuàng)建的一些類,使用單例可以降低系統(tǒng)的內(nèi)存壓力,減少 GC。
某類只要求生成一個(gè)對(duì)象的時(shí)候,如一個(gè)班中的班長(zhǎng)、每個(gè)人的身份證號(hào)等。
某些類創(chuàng)建實(shí)例時(shí)占用資源較多,或?qū)嵗臅r(shí)較長(zhǎng),且經(jīng)常使用。
某類需要頻繁實(shí)例化,而創(chuàng)建的對(duì)象又頻繁被銷毀的時(shí)候,如多線程的線程池、網(wǎng)絡(luò)連接池等。
頻繁訪問數(shù)據(jù)庫(kù)或文件的對(duì)象。
對(duì)于一些控制硬件級(jí)別的操作,或者從系統(tǒng)上來講應(yīng)當(dāng)是單一控制邏輯的操作,如果有多個(gè)實(shí)例,則系統(tǒng)會(huì)完全亂套。
當(dāng)對(duì)象需要被共享的場(chǎng)合。由于單例模式只允許創(chuàng)建一個(gè)對(duì)象,共享該對(duì)象可以節(jié)省內(nèi)存,并加快對(duì)象訪問速度。如 Web 中的配置對(duì)象、數(shù)據(jù)庫(kù)的連接池等。
1.2、單利模式的結(jié)構(gòu)
單例類:包含一個(gè)實(shí)例且能自行創(chuàng)建這個(gè)實(shí)例的類。
訪問類:使用單例的類。
2.1單利模式的實(shí)現(xiàn)方式一:懶漢式
該模式的特點(diǎn)是類加載時(shí)沒有生成單例,只有當(dāng)?shù)谝淮握{(diào)用 getlnstance 方法時(shí)才去創(chuàng)建這個(gè)單例。
代碼:
public class LazySingleton { //構(gòu)造器私有,堵死了外界利用new創(chuàng)建此類對(duì)象的可能 private LazySingleton(){ System.out.println("yese"); } //提供對(duì)象,是外界獲取本類對(duì)象的唯一全局訪問點(diǎn) private static LazySingleton lazySingleton; public static LazySingleton getInstance(){ //如果對(duì)象不存在,就new一個(gè)新的對(duì)象,否則返回已有的對(duì)象 if(lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }
@Test public void testLazy() { LazySingleton l1 = LazySingleton.getInstance(); LazySingleton l2 = LazySingleton.getInstance(); System.out.println(l1+"\n" + l2); System.out.println(l1 == l2); }
測(cè)試結(jié)果:
com.singletonPattern.LazySingleton@78e03bb5
com.singletonPattern.LazySingleton@78e03bb5
true
該代碼很顯然是不適合多線程模式的,這時(shí)候就需要給單例模式的對(duì)象加上一把鎖
class LazySingleton2{ //構(gòu)造器私有,堵死了外界利用new創(chuàng)建此類對(duì)象的可能 private LazySingleton2(){ System.out.println("s2"); } //提供對(duì)象,是外界獲取本類對(duì)象的唯一全局訪問點(diǎn) private static LazySingleton2 lazySingleton2; public static LazySingleton2 getInstance(){ //如果對(duì)象不存在,就new一個(gè)新的對(duì)象,否則返回已有的對(duì)象 if(lazySingleton2 == null) { synchronized(LazySingleton2.class) { if(lazySingleton2 == null) { lazySingleton2 = new LazySingleton2(); } } } return lazySingleton2; } }
這時(shí)還有一個(gè)問題,就是在創(chuàng)建一個(gè)對(duì)象時(shí),在JVM中會(huì)經(jīng)過三步:
(1)為對(duì)象分配內(nèi)存空間
(2)初始化該對(duì)象
(3)將該對(duì)象指向分配好的內(nèi)存空間
那么在執(zhí)行我們這個(gè)多線程代碼的過程中,有可能他不按照這三部的順序走,那么就會(huì)導(dǎo)致一個(gè)指令重排的問題,解決此問題的方法就是加關(guān)鍵字volatile
class LazySingleton3{ //構(gòu)造器私有,堵死了外界利用new創(chuàng)建此類對(duì)象的可能 private LazySingleton3(){ } //提供對(duì)象,是外界獲取本類對(duì)象的唯一全局訪問點(diǎn) private volatile static LazySingleton3 lazySingleton3; public static LazySingleton3 getInstance(){ //如果對(duì)象不存在,就new一個(gè)新的對(duì)象,否則返回已有的對(duì)象 if(lazySingleton3 == null) { synchronized(LazySingleton3.class) { if(lazySingleton3 == null) { lazySingleton3 = new LazySingleton3(); } } } return lazySingleton3; } }
最后我們還可以去解決一下反射來破壞單利模式
class LazySingleton4 { //提供變量,控制反射 public static boolean s = false; //構(gòu)造器私有,堵死了外界利用new創(chuàng)建此類對(duì)象的可能 private LazySingleton4() { if (s == false) { //當(dāng)?shù)谝粋€(gè)對(duì)象成功獲取之后,第二個(gè)對(duì)象就不能通過反射獲取了 s = true; } else { throw new RuntimeException("不要用反射破壞異常"); } } //提供對(duì)象,是外界獲取本類對(duì)象的唯一全局訪問點(diǎn) private volatile static LazySingleton4 lazySingleton4; public static LazySingleton4 getInstance() { //如果對(duì)象不存在,就new一個(gè)新的對(duì)象,否則返回已有的對(duì)象 if (lazySingleton4 == null) { synchronized (LazySingleton4.class) { if (lazySingleton4 == null) { lazySingleton4 = new LazySingleton4(); } } } return lazySingleton4; } }
volatile, synchronized這兩個(gè)關(guān)鍵字就能保證線程安全,但是每次訪問時(shí)都要同步,會(huì)影響性能,且消耗更多的資源,這是懶漢式單例的缺點(diǎn)。
2.2單利模式的實(shí)現(xiàn)方式一:餓漢式
該模式的特點(diǎn)是類一旦加載就創(chuàng)建一個(gè)單例,保證在調(diào)用 getInstance 方法之前單例已經(jīng)存在了。
代碼:
public class HungrySingleton { private static final HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return instance; } }
餓漢式單例在類創(chuàng)建的同時(shí)就已經(jīng)創(chuàng)建好一個(gè)靜態(tài)的對(duì)象供系統(tǒng)使用,以后不再改變,所以是線程安全的,可以直接用于多線程而不會(huì)出現(xiàn)問題。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式
這篇文章主要介紹了Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12springboot中項(xiàng)目啟動(dòng)時(shí)實(shí)現(xiàn)初始化方法加載參數(shù)
這篇文章主要介紹了springboot中項(xiàng)目啟動(dòng)時(shí)實(shí)現(xiàn)初始化方法加載參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12一文講透為什么遍歷LinkedList要用增強(qiáng)型for循環(huán)
這篇文章主要為大家介紹了為什么遍歷LinkedList要用增強(qiáng)型for循環(huán)的透徹詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Spring Boot啟動(dòng)banner定制的步驟詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot啟動(dòng)banner定制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Spring Data Jpa多表查詢返回自定義實(shí)體方式
這篇文章主要介紹了Spring Data Jpa多表查詢返回自定義實(shí)體方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02idea啟動(dòng)springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法
這篇文章主要介紹了idea啟動(dòng)springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Spring實(shí)戰(zhàn)之使用XML方式管理聲明式事務(wù)操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之使用XML方式管理聲明式事務(wù)操作,結(jié)合實(shí)例形式詳細(xì)分析了Spring XML方式管理聲明式事務(wù)具體步驟、配置、接口及使用技巧,需要的朋友可以參考下2020-01-01