單例模式只會(huì)懶漢餓漢?讀完本篇讓你面試瘋狂加分

前言

說(shuō)到設(shè)計(jì)模式,面試排在第一位的十有八九是單例模式,這一定是大部分人從入門到面試工作都避不開(kāi)的基礎(chǔ)知識(shí)。

但單例模式不僅有懶漢模式和餓漢模式兩種寫(xiě)法,往往我們掌握的都是最基礎(chǔ)的寫(xiě)法,如果你有閱讀過(guò)類似spring這樣的知名框架源碼,一定會(huì)發(fā)現(xiàn)他們的單例模式寫(xiě)法和你所掌握的完全不同。

本篇就給大家?guī)?lái)單例模式從基礎(chǔ)->最優(yōu)->額外推薦的寫(xiě)法,幫助你面試瘋狂加分。


懶漢餓漢

1、餓漢模式

餓漢模式簡(jiǎn)單理解就是提前創(chuàng)建好了對(duì)象

優(yōu)點(diǎn):寫(xiě)法簡(jiǎn)單,沒(méi)有線程同步的問(wèn)題

缺點(diǎn):因?yàn)橐崆皠?chuàng)建好對(duì)象,不管使用與否都一直占著內(nèi)存

推薦:對(duì)象較小且簡(jiǎn)單則使用餓漢模式

public final class Singleton { 
    // 創(chuàng)建好實(shí)例 
    private static Singleton instance = new Singleton();
    // 構(gòu)造函數(shù) 
    private Singleton() {} 
    // 獲取實(shí)例 
    public static Singleton getInstance() {
        return instance; 
    } 
}


2、懶漢模式

懶漢模式簡(jiǎn)單理解就是在需要時(shí)才創(chuàng)建對(duì)象

優(yōu)點(diǎn):懶加載方式性能更高

缺點(diǎn):要考慮多線程的同步問(wèn)題

推薦:只要不符合上面餓漢的推薦使用條件則都使用懶漢模式

public final class Singleton { 
    private static Singleton instance = null;
    // 構(gòu)造函數(shù) 
    private Singleton() {} 
    // 獲取實(shí)例
    public static Singleton getInstance() {
        // 為null時(shí)才實(shí)例化對(duì)象
        if (null == instance) {
            instance = new Singleton();
        } 
        return instance;
    } 
}


同步鎖

上面介紹了懶漢的缺點(diǎn)是多線程同步的問(wèn)題,那么馬上就能想到使用同步鎖來(lái)解決這個(gè)問(wèn)題。

這里使用synchronized關(guān)鍵字且通過(guò)代碼塊來(lái)降低鎖粒度,最大程度保證了性能開(kāi)銷,其實(shí)從java8以后,synchronized的性能已經(jīng)有了較大提升。

public final class Singleton { 
    private static Singleton instance = null;
    // 構(gòu)造函數(shù) 
    private Singleton() {}
    // 獲取實(shí)例
    public static Singleton getInstance() {
        // 獲取對(duì)象時(shí)加上同步鎖
        if (null == instance) {
            synchronized (Singleton.class) { 
                instance = new Singleton();
            } 
        } 
        return instance;
    } 
}


雙重檢查鎖

上面雖然使用了同步鎖代碼塊,勉強(qiáng)解決了線程同步的問(wèn)題且性能開(kāi)銷做了最大程度的優(yōu)化,可實(shí)際上在多線程環(huán)境下仍然存在線程安全問(wèn)題。

當(dāng)依然有多個(gè)線程進(jìn)入到if判斷里面時(shí),這個(gè)線程安全問(wèn)題還是存在,雖然這種情況并非一定出現(xiàn),可極端情況下出現(xiàn)的幾率非常大。

這個(gè)時(shí)候就需要使用面試中關(guān)于設(shè)計(jì)模式很喜歡問(wèn)到的DCL即雙重檢查鎖模式,聽(tīng)起來(lái)很高大上,其實(shí)就是多加了一層判斷。

說(shuō)白了,就是在進(jìn)入同步鎖之前和之后分別進(jìn)行了檢查,極大降低了線程安全問(wèn)題。

public final class Singleton { 
    private static Singleton instance = null;
    // 構(gòu)造函數(shù) 
    private Singleton() {} 
    // 獲取實(shí)例
    public static Singleton getInstance() {
        // 第一次判斷,當(dāng)instance為null時(shí)則實(shí)例化對(duì)象
        if(null == instance) {
            synchronized (Singleton.class) {
                // 第二次判斷,放在同步鎖中,當(dāng)instance為null時(shí)則實(shí)例化對(duì)象 
                if(null == instance) {
                    instance = new Singleton();
                } 
            } 
        } 
        return instance;
    } 
}


最優(yōu)雙重檢查鎖

雙重檢查鎖方式是單例懶漢模式在多線程下處理安全問(wèn)題的最佳方案之一,但上面依然不是最優(yōu)寫(xiě)法。

這里就要引出一個(gè)「指令重排」的概念,這個(gè)概念是java內(nèi)存模型中的,我這里用最簡(jiǎn)潔的方式幫你理解。

Java中new一個(gè)對(duì)象在內(nèi)存中執(zhí)行指令的正常順序是:分配 -> 創(chuàng)建 -> 引用,而多線程環(huán)境下,JVM出于對(duì)語(yǔ)句的優(yōu)化,有可能重排順序:分配 -> 引用 -> 創(chuàng)建。

如果出現(xiàn)這種情況,那么上面的雙重檢查鎖方式依然無(wú)法解決線程安全問(wèn)題。

解決方式很簡(jiǎn)單,加個(gè)volatile關(guān)鍵字即可。

volatile關(guān)鍵字作用:保證可見(jiàn)性和有序性。

public final class Singleton { 
    // 加上volatile關(guān)鍵字
    private volatile static Singleton instance = null;
    // 構(gòu)造函數(shù) 
    private Singleton() {} 
    // 獲取實(shí)例
    public static Singleton getInstance() {
        // 第一次判斷,當(dāng)instance為null時(shí)則實(shí)例化對(duì)象
        if(null == instance) {
            synchronized (Singleton.class) {
                // 第二次判斷,放在同步鎖中,當(dāng)instance為null時(shí)則實(shí)例化對(duì)象 
                if(null == instance) {
                    instance = new Singleton();
                } 
            } 
        } 
        return instance;
    } 
}


枚舉模式

《Effective Java》是Java業(yè)界非常受歡迎的一本書(shū),對(duì)于想要在Java領(lǐng)域深耕的程序員來(lái)講,這本書(shū)沒(méi)有不看的理由,相信很多Java程序員不管看過(guò)還是沒(méi)看過(guò),都有聽(tīng)過(guò)這本書(shū)。

而這本書(shū)的作者,所推薦的一種單例設(shè)計(jì)模式寫(xiě)法,就是枚舉方式。

原理十分簡(jiǎn)單,在Java中枚舉類的域在編譯后會(huì)被聲明為static屬性,而JVM會(huì)保證static修飾的成員變量只被實(shí)例化一次。

public class Singleton {

   // 構(gòu)造函數(shù)
   private Singleton() {
   }

   // 從枚舉中獲取實(shí)例
   public static Singleton getInstance() {
      return SingletonEnum.SINGLETON.getInstance();
   }

   // 定義枚舉
   private enum SingletonEnum {
      SINGLETON;

      private Singleton instance;

      // JVM保證這個(gè)方法只調(diào)用一次
      SingletonEnum() {
         instance = new Singleton();
      }

      public Singleton getInstance() {
         return instance;
      }
   }
}


總結(jié)

最后這里稍微提一下,以免部分人對(duì)于設(shè)計(jì)模式感到些許負(fù)擔(dān)。

單例模式其實(shí)很簡(jiǎn)單,餓漢模式和懶漢模式在許多開(kāi)源框架中應(yīng)用都比較廣泛,甚至餓漢模式用的更多,比如Java的Runtime類中就這么干的,簡(jiǎn)單粗暴,有興趣的可以自己看下源碼。

難道這些框架的作者就意識(shí)不到本篇中講述的問(wèn)題嗎,并非如此,用哪種方式編寫(xiě)單例模式往往視情況而定,一些理論上會(huì)發(fā)生的問(wèn)題往往實(shí)際中可以忽略不計(jì),此時(shí)更傾向于使用最簡(jiǎn)單直接的寫(xiě)法。

真正難的其實(shí)還是面試,不少關(guān)于單例模式的問(wèn)題中喜歡問(wèn)到它的幾種寫(xiě)法、存在的問(wèn)題以及最佳方案,說(shuō)白了還是面試造核彈,進(jìn)廠擰螺絲,目的是想知道你對(duì)設(shè)計(jì)模式的了解程度,從而評(píng)判你研究該學(xué)科的態(tài)度及造詣。

因此,大家看完本篇可以手動(dòng)嘗試著寫(xiě)一寫(xiě),了解一些也就夠了,沒(méi)必要過(guò)分深究,因?yàn)镴ava領(lǐng)域需要花費(fèi)精力的地方確實(shí)太多了。


心得

最后說(shuō)下我的一點(diǎn)心得,所謂設(shè)計(jì)模式固然能給Java代碼本身帶來(lái)更多優(yōu)雅,但是寫(xiě)了很多年Java代碼,我大體還是覺(jué)得Java本身的裝飾實(shí)在太多,優(yōu)雅換來(lái)的往往是代碼本身的負(fù)擔(dān)。

我參與過(guò)的研發(fā)團(tuán)隊(duì)中,幾乎都能見(jiàn)到許多工程師編寫(xiě)的比較優(yōu)雅的代碼,一些設(shè)計(jì)模式也寫(xiě)的很好,可帶來(lái)的問(wèn)題也很明顯,就是可讀性越來(lái)越差,要求每個(gè)團(tuán)員都對(duì)Java有較高的造詣,甚至在某些時(shí)候給人力資源帶來(lái)壓力,這從實(shí)際角度考慮是不妥的。

我更多的建議是,應(yīng)對(duì)面試或?qū)W習(xí)好好領(lǐng)悟設(shè)計(jì)模式,百利而無(wú)一害,但實(shí)際工作中盡量少用復(fù)雜的設(shè)計(jì)模式,以簡(jiǎn)潔直接的代碼為主,有利于整個(gè)團(tuán)隊(duì)后期維護(hù),甚至加快人員變更后新成員對(duì)項(xiàng)目的適應(yīng)度,因?yàn)楣ぷ髡f(shuō)白了還是以績(jī)效為主,怎么簡(jiǎn)單高效怎么來(lái)就行,你自己的個(gè)人項(xiàng)目你想怎么玩隨便你。



本人原創(chuàng)文章純手打,覺(jué)得有一滴滴幫助的話就請(qǐng)點(diǎn)個(gè)贊和收藏吧~

本人長(zhǎng)期分享工作中的感悟、經(jīng)驗(yàn)及實(shí)用案例,喜歡的話也可以進(jìn)入個(gè)人主頁(yè)關(guān)注一下哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容