您好,登錄后才能下訂單哦!
這篇文章給大家介紹深入淺析Java設(shè)計(jì)模式中的單例模式,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
單例模式是非常常見的設(shè)計(jì)模式,其含義也很簡單,一個(gè)類給外部提供一個(gè)唯一的實(shí)例。下文所有的代碼均在github
源碼整個(gè)項(xiàng)目不僅僅有設(shè)計(jì)模式,還有其他JavaSE知識(shí)點(diǎn),歡迎Star,F(xiàn)ork
單例模式的UML圖
單例模式的關(guān)鍵點(diǎn)
通過上面的UML圖,我們可以看出單例模式的特點(diǎn)如下:
1、構(gòu)造器是私有的,不允許外部的類調(diào)用構(gòu)造器
2、提供一個(gè)供外部訪問的方法,該方法返回單例類的實(shí)例
如何實(shí)現(xiàn)單例模式
上面已經(jīng)給出了單例模式的關(guān)鍵點(diǎn),我們的實(shí)現(xiàn)只需要滿足上面2點(diǎn)即可。但是正因?yàn)閱卫J降膶?shí)現(xiàn)方式比較寬松,所以不同的實(shí)現(xiàn)方式會(huì)有不同的問題。我們可以對(duì)單例模式的實(shí)現(xiàn)做一下分類,看一看有哪些不同的實(shí)現(xiàn)方式。
1根據(jù)單例對(duì)象的創(chuàng)建時(shí)機(jī)不同,可以分為餓漢模式和懶漢模式。餓漢是指在類加載的時(shí)候,就創(chuàng)建了對(duì)象。但是創(chuàng)建對(duì)象有時(shí)比較消耗資源,會(huì)造成類加載很慢,但是優(yōu)點(diǎn)是獲取對(duì)象的速度很快,因?yàn)樵缫呀?jīng)創(chuàng)建好了嘛。懶漢就是相對(duì)餓漢而言,在需要返回單例對(duì)象的時(shí)候,在創(chuàng)建對(duì)象,類加載的時(shí)候,并不初始化,好處與缺點(diǎn)也不言而喻
2.根據(jù)是否實(shí)現(xiàn)線程安全,可以分為普通的懶漢模式這種線程不安全的寫法,和餓漢模式,雙重檢查鎖的懶漢模式,以及通過靜態(tài)內(nèi)部類或者枚舉類等實(shí)現(xiàn)的線程安全的寫法。
一個(gè)線程不安全的單例模式
public class SimpleSingleton { private static SimpleSingleton simpleSingleton; private SimpleSingleton(){ } public static SimpleSingleton getInstance(){ if (simpleSingleton == null) { simpleSingleton = new SimpleSingleton(); } return simpleSingleton; } }
首先,我們可以看出這是一個(gè)懶漢模式的實(shí)現(xiàn)。因?yàn)橹挥性趃etInstance的時(shí)候,才會(huì)真正創(chuàng)建單例的對(duì)象。但是為什么他是線程不安全的呢,是因?yàn)榭赡軙?huì)有2個(gè)線程同時(shí)進(jìn)入if (simpleSingleton == null)的判斷,就是同時(shí)創(chuàng)建了simpleSingleton對(duì)象。
DCL懶漢模式
上面的方法可以看出是存在線程不安全的問題的,我們可以用同步關(guān)鍵字synchronized來實(shí)現(xiàn)線程安全。我們先逐步分析,先用synchronized來改寫上面的懶漢模式,代碼如下:
public class DCLSingleton { private static DCLSingleton singleton; private DCLSingleton(){ } public synchronized static DClSingleton getSingleton(){ if (singleton == null) { singleton = new DCLSingleton(); } return singleton; } }
這樣,就有效的保證了不會(huì)有兩個(gè)線程同時(shí)執(zhí)行該方法,但這個(gè)效率也太低了吧。因?yàn)樵趧?chuàng)建實(shí)例之后,每次得到實(shí)例對(duì)象,還是需要進(jìn)行同步,synchronized的同步保證代價(jià)是比較大的,因此可以在此基礎(chǔ)上進(jìn)行改造。在已經(jīng)創(chuàng)建好之后,就不需要同步了,我們可以改成如下的形式:
public static DCLSingleton getSingleton(){ if (singleton == null) { synchronized (DCLSingleton.class) { if (singleton == null) { singleton = new DCLSingleton(); } } } return singleton; }
其他代碼不變,只看這個(gè)方法。該方法的兩重if (singleton == null)可以有效地保證線程安全。比如,當(dāng)兩個(gè)線程同時(shí)進(jìn)入該方法的時(shí)候,第一個(gè)if,兩者都是進(jìn)入,下面的代碼,但是碰到同步代碼塊,只能有一個(gè)先進(jìn)入,進(jìn)入的時(shí)候,繼續(xù)判斷,再次判斷為空,才會(huì)真正創(chuàng)建對(duì)象。如果不進(jìn)行,第二個(gè)判斷,那些對(duì)于第一個(gè)進(jìn)入的線程而言,確實(shí)創(chuàng)建了對(duì)象,但是第二個(gè)線程,他緊接著也會(huì)執(zhí)行創(chuàng)建對(duì)象的操作,因?yàn)椴恢赖谝粋€(gè)線程已經(jīng)創(chuàng)建成功。因此,需要兩次判空。
但是真的就如此簡單的保證了線程安全嗎?我們仔細(xì)分析一下這個(gè)過程,singleton = new DCLSingleton();這個(gè)代碼實(shí)際上是3個(gè)操作。
1.給DCLSingleton實(shí)例分配內(nèi)存
2.調(diào)用DCLSingleton()的構(gòu)造函數(shù),初始化成員字段
3.將singleton對(duì)象指向分配的內(nèi)存空間。
在JDK1.5以前,上面的3個(gè)執(zhí)行順序是不固定的,有可能是1-2-3,或者1-3-2。如果是1-3-2,則在第一個(gè)線程執(zhí)行完第三步以后,第二個(gè)線程立即執(zhí)行,但還沒有真正的進(jìn)行初始化,所以就會(huì)使用的時(shí)候出錯(cuò)。在JDK1.5以后,我們可以用volatile關(guān)鍵字來保證該1-2-3的順序執(zhí)行。所以,除了getSingleton()方法要改成上面的樣子以外,還需要對(duì)private static DCLSingleton singleton; 改寫成private static volatile DCLSingleton singleton; 這樣,就真正保證了線程同步的懶漢寫法的單例模式。
餓漢寫法
餓漢寫法有很多變形,但無論是哪一種變形,都能保證線程安全,因?yàn)轲I漢寫法是在類加載的時(shí)候,就完成了對(duì)象的初始化,類加載保證了他們天生是線程安全的。下面給出常見的2中餓漢寫法
public class HungrySingleton { private static final HungrySingleton singleton = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getSingleton(){ return singleton; } } public class HungrySingleton { private static final HungrySingleton singleton = new HungrySingleton(); private HungrySingleton(){ } // public static HungrySingleton getSingleton(){ // return singleton; // } }
這兩種對(duì)初始化單例的對(duì)象上面,都是一致的, 通過final來保證對(duì)象的唯一。不同的是,調(diào)用單例對(duì)象的方式,第一種是通過getSingleton(),第二種是通過類.類變量的形式。
靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式
雙重檢查鎖(DCL)實(shí)現(xiàn)單例模式,雖然解決了線程不安全的問題,以及保證了資源的懶加載,在需要的時(shí)候,才會(huì)進(jìn)行實(shí)例化的操作。但是在某些情況下(比如JDK低于1.5)會(huì)出現(xiàn)DCL失效,所以有一種很簡潔且依舊是懶加載的方法實(shí)現(xiàn)單例模式。寫法如下:
public class StaticSingleton { private StaticSingleton(){ } public static final StaticSingleton getInstance(){ return Holder.singleton; } private static class Holder{ private static final StaticSingleton singleton = new StaticSingleton(); } }
通過靜態(tài)內(nèi)部類的形式,實(shí)現(xiàn)單例類的初始化,其特性同樣是通過ClassLoader來保證其單例對(duì)象的唯一,但是這是懶加載的,因?yàn)橹挥性贖older類被調(diào)用的時(shí)候,即getInstance調(diào)用的時(shí)候,才會(huì)加載Holder類從而實(shí)現(xiàn)創(chuàng)建對(duì)象。
枚舉類實(shí)現(xiàn)單例模式
直接看代碼:
public enum EnumSingleton { SINGLETON; public void doSometings(){ } }
使用的時(shí)候,直接通過EnumSingleton.SINGLETON.doSomethings()。枚舉類天生特性是保證不會(huì)有兩個(gè)實(shí)例,并且只有在第一次訪問的時(shí)候才會(huì)被實(shí)例化,是懶加載的情況。
真的不會(huì)再次創(chuàng)建新的對(duì)象嗎?
在常規(guī)調(diào)用單例類的getInstance()方法的情況下,使用線程安全的寫法確實(shí)不會(huì)創(chuàng)建新的對(duì)象,但是Java提供了很多奇特的技巧和使用,下面這些使用會(huì)破壞掉常規(guī)的單例。
在除了枚舉實(shí)現(xiàn)單例模式的方法以外,其余所有方法碰到上述四種情況,都會(huì)重新創(chuàng)建對(duì)象。原因如下:
什么時(shí)候用單例模式,用哪一種寫法的單例模式
單例模式有兩種比較適合的使用場(chǎng)景。
第一種是創(chuàng)建某個(gè)對(duì)象,需要的代價(jià)比較大,為了避免頻繁的創(chuàng)建和銷毀對(duì)象從而引起的對(duì)資源的浪費(fèi),會(huì)考慮使用單例模式。
第二種是這個(gè)對(duì)象必須只有一個(gè),有多個(gè)會(huì)造成不可預(yù)估的錯(cuò)誤,或者程序的混亂,比如只會(huì)有一個(gè)序號(hào)生成器,一個(gè)緩存等等。
針對(duì)使用的單例模式,如果需要理解的加載資源,就是用餓漢寫法,在Android應(yīng)用中,很多對(duì)象需要在啟動(dòng)的時(shí)候,立即就使用,比如啟動(dòng)時(shí),需要拉取相機(jī)配置的類管理縮略圖的cache類等等。如果不是立即需要,或者不是貫穿應(yīng)用始終的,就不需要使用餓漢寫法,可以考慮懶漢寫法用(DCL或者靜態(tài)內(nèi)部類實(shí)現(xiàn))這兩種在一般情況下都不會(huì)出現(xiàn)問題。
關(guān)于深入淺析Java設(shè)計(jì)模式中的單例模式就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。