單例模式的幾種寫法

一、單例模式概述

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

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

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

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

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

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

二、單例模式示例

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

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

2.1 餓漢式

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

public class Java2y {

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

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

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

2.2 簡單餓漢式

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

public class Java2y {

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

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

上面那種直接在方法上加鎖的方式其實不夠好,因為在方法上加了內(nèi)置鎖,在多線程環(huán)境下性能會比較低,所以我們可以將鎖的范圍縮小。

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;
    }
}

這樣寫以后可行了嗎?不行,因為雖然加了鎖,但還是有可能創(chuàng)建出兩個對象出來:

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

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

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(){
        // 這個判空是為了提高性能
        if(y == null){
            // 將鎖的范圍縮小,提高性能 
            synchronized(Java2y.class){
                if(y == null){
                    y = new Java2y();
                }
            }
        }
        return y;
    }
}

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

它的原理是這樣的:

  • 當任何一個線程第一次調(diào)用getInstance()時,都會使SingletonHolder被加載和被初始化,此時靜態(tài)初始化器將執(zhí)行Singleton的初始化操作。(被調(diào)用時才進行初始化?。?/li>
  • 初始化靜態(tài)數(shù)據(jù)時,Java提供了的線程安全性保證。(所以不需要任何的同步)
public class Java2y {
    
    private Java2y (){}
    
    // 使用私有靜態(tài)內(nèi)部類實現(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,
}

這種實現(xiàn):

  • 簡單
  • 防止多次實例化,即使是在復(fù)雜序列化或者反射攻擊時也很安全
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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

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