單例模式的幾種寫法

一、單例模式概述

單例模式定義很簡單:一個(gè)類中能創(chuàng)建一個(gè)實(shí)例,所以稱之為單例。
那我們?yōu)槭裁匆褂脝卫J侥兀?/p>

  • 那既然一個(gè)類中只能創(chuàng)建一個(gè)實(shí)例,那么可以說這是跟類的狀態(tài)與對(duì)象無關(guān)的了。
  • 頻繁創(chuàng)建對(duì)象、管理對(duì)象是一件耗費(fèi)資源的事,我們只需要?jiǎng)?chuàng)建一個(gè)對(duì)象來用就足夠了。

如果你學(xué)過J2EE,你可能知道:

  • Servlet是單例
  • Struts2是多例
  • SpringMVC是單例的

Struts2為啥設(shè)計(jì)成多例呢?

  • 這主要是由于設(shè)計(jì)層面上的問題,Struts2是基于Filter攔截類的,ognl引擎對(duì)變量是注入的,所以它要設(shè)計(jì)成多例

二、單例模式示例

編寫單例模式的代碼其實(shí)很簡單,分為三步:

  • 將構(gòu)造函數(shù)私有化
  • 在類的內(nèi)部創(chuàng)建示例
  • 提供獲取唯一實(shí)例的方法

2.1 餓漢式

根據(jù)上面的步驟,我們就可以創(chuàng)建單例模式了。

public class Java2y {

    // 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對(duì)象
    private Java2y(){}
    
    // 2.在類的內(nèi)部創(chuàng)建實(shí)例
    private static Java2y y = new Java2y();
    
    // 3.提供獲取唯一實(shí)例的方法
    public static Java2y getInstance(){
        return y;
    }
}

這種代碼我們稱之為“餓漢式”:

  • 一上來就創(chuàng)建了對(duì)象,如果該實(shí)例從始至終都沒被使用過,則會(huì)造成內(nèi)存浪費(fèi)。

2.2 簡單餓漢式

既然一上來就創(chuàng)建對(duì)象會(huì)造成內(nèi)存浪費(fèi),那我們設(shè)計(jì)成用到的時(shí)候再創(chuàng)建對(duì)象

public class Java2y {

    // 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對(duì)象
    private Java2y(){}
    
    // 2.1先不創(chuàng)建對(duì)象,等用到的時(shí)候再創(chuàng)建
    private static Java2y y = null;
    
    // 2.2調(diào)用這個(gè)方法,創(chuàng)建對(duì)象
    public static Java2y getInstance(){
        // 3.如果對(duì)象為null,就創(chuàng)建并返回
        if(y == null){
            y = new Java2y();
        }
        return y;
    }
}

上面的代碼不行嗎?在單線程環(huán)境下是可行的,如果在多線程環(huán)境下就有問題了,解決方法也很簡單,加鎖就行。

public class Java2y {

    // 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對(duì)象
    private Java2y(){}
    
    // 2.1先不創(chuàng)建對(duì)象,等用到的時(shí)候再創(chuàng)建
    private static Java2y y = null;
    
    // 2.2調(diào)用這個(gè)方法,創(chuàng)建對(duì)象
    public static synchronized Java2y getInstance(){
        // 3.如果對(duì)象為null,就創(chuàng)建并返回
        if(y == null){
            y = new Java2y();
        }
        return y;
    }
}

2.3 雙重檢測機(jī)制(DCL)懶漢式

上面那種直接在方法上加鎖的方式其實(shí)不夠好,因?yàn)樵诜椒ㄉ霞恿藘?nèi)置鎖,在多線程環(huán)境下性能會(huì)比較低,所以我們可以將鎖的范圍縮小。

public class Java2y {
    
    private Java2y(){}
    
    private static Java2y y = null;
    
    private static Java2y getInstance(){
        if(y == null){
            // 將鎖的范圍縮小,提高性能 
            synchronized(Java2y.class){
                y = new Java2y();
            }
        }
        return y;
    }
}

這樣寫以后可行了嗎?不行,因?yàn)殡m然加了鎖,但還是有可能創(chuàng)建出兩個(gè)對(duì)象出來:

  • 線程1和線程2同時(shí)調(diào)用getInstance()方法,它們同時(shí)判斷y==null,因?yàn)榻Y(jié)果都為null,所以進(jìn)入了if代碼塊。
  • 此時(shí)線程1得到CPU的控制權(quán)-->進(jìn)入同步代碼塊-->創(chuàng)建對(duì)象-->返回對(duì)象
  • 線程1完成后,線程2得到了CPU控制權(quán),一樣是進(jìn)入同步代碼塊-->創(chuàng)建對(duì)象-->返回對(duì)象
  • 然后很明顯,最終返回了不止一個(gè)對(duì)象

然后有人又想到了:進(jìn)入同步代碼塊時(shí)再判斷一下對(duì)象是否存在就行了吧,于是有了下面的代碼:

public class Java2y {
    
    private Java2y(){}
    
    private static Java2y y = null;
    
    private static Java2y getInstance(){
        if(y == null){
            // 將鎖的范圍縮小,提高性能 
            synchronized(Java2y.class){
                if(y == null){
                    y = new Java2y();
                }
            }
        }
        return y;
    }
}

然后這種方式又出現(xiàn)了重排序的問題?。?!怎么解決呢?加上volatile關(guān)鍵字吧,volatile有內(nèi)存屏障的功能!

所以說完整的DCL代碼是這樣子的:

public class Java2y {
    
    private Java2y(){}
    
    private static volatile Java2y y = null;
    
    private static Java2y getInstance(){
        // 這個(gè)判空是為了提高性能
        if(y == null){
            // 將鎖的范圍縮小,提高性能 
            synchronized(Java2y.class){
                if(y == null){
                    y = new Java2y();
                }
            }
        }
        return y;
    }
}

2.4 靜態(tài)內(nèi)部類懶漢式

它的原理是這樣的:

  • 當(dāng)任何一個(gè)線程第一次調(diào)用getInstance()時(shí),都會(huì)使SingletonHolder被加載和被初始化,此時(shí)靜態(tài)初始化器將執(zhí)行Singleton的初始化操作。(被調(diào)用時(shí)才進(jìn)行初始化?。?/li>
  • 初始化靜態(tài)數(shù)據(jù)時(shí),Java提供了的線程安全性保證。(所以不需要任何的同步)
public class Java2y {
    
    private Java2y (){}
    
    // 使用私有靜態(tài)內(nèi)部類實(shí)現(xiàn)懶加載
    private static class LazyHolder {
        private static final Java2y INSTANCE = new Java2y();
    }
    
    public static Java2y getInstance(){
        return LazyHolder.INSTANCE;
    }
}

這種方式非常推薦使用!??!

2.5 枚舉方式

public enum Java2y {
    JAVA_2_y,
}

這種實(shí)現(xiàn):

  • 簡單
  • 防止多次實(shí)例化,即使是在復(fù)雜序列化或者反射攻擊時(shí)也很安全
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • (1)餓漢模式 分析: 第一次加載到內(nèi)存中的就會(huì)被初始化 ,并對(duì)外提供一個(gè)獲取該實(shí)例對(duì)象的方法,優(yōu)點(diǎn):立即執(zhí)行初始...
    小杰的快樂時(shí)光閱讀 411評(píng)論 0 0
  • 0x01 單例模式簡介 單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只...
    FlyingPig_閱讀 469評(píng)論 0 1
  • 1.餓漢private static Singleton instance=new Singleton();或pr...
    藍(lán)灰_q閱讀 948評(píng)論 0 0
  • 應(yīng)用最廣的模式——單例模式 一、單例模式的定義和關(guān)鍵點(diǎn) 定義確保一個(gè)類只有一個(gè)實(shí)例對(duì)象,而且自行實(shí)例化向整個(gè)系統(tǒng)提...
    zoustin閱讀 511評(píng)論 0 1
  • 一、有什么用 保證應(yīng)用程序中只有該類的一個(gè)實(shí)例 二、特點(diǎn) 好的單例模式應(yīng)保證:1)線程安全;2)延遲加載:真正使用...
    四喜湯圓閱讀 190評(píng)論 0 0

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