二、單例模式

單例設(shè)計模式是最簡單的一種創(chuàng)建型設(shè)計模式,其提供了創(chuàng)建對象的最佳實現(xiàn)。該模式涉及到一個單一的類,且該類負責(zé)創(chuàng)建自己的對象,同時確保只有一個對象被創(chuàng)建。該類提供了一種訪問其唯一對象的方式,訪問方式的實現(xiàn)是單例設(shè)計模式的核心,涉及到線程安全等問題。

  • 為何使用單例模式?
    通常來說,我們獲取到一個對象實例,當(dāng)只使用其內(nèi)部提供的方法,而不關(guān)注其成員變量時,可以使用單例模式。
    • 節(jié)省內(nèi)存空間:單例在內(nèi)存中只有一個實例對象,節(jié)省內(nèi)存空間,避免大量創(chuàng)建及銷毀
    • 高性能:減少高質(zhì)量資源重復(fù)占用,可以進行全部訪問

單例模式理論

單例模式

  • 什么時候使用單例模式?
    • 重復(fù)對象需要頻繁實例化及銷毀的對象
    • 有狀態(tài)化的工具對象
    • 頻繁訪問數(shù)據(jù)庫或文件的對象

單例角色:單例模式只有一個單例角色,在單例的內(nèi)部生成一個對象實例,同時提供一個方法用于獲取這個對象實例。為了避免外界直接創(chuàng)建對象實例從而破壞單例模式,通常構(gòu)造函數(shù)都是設(shè)置為私有的,外部只能通過方法獲取到唯一的單例對象。

單例模式的實現(xiàn)方式

比較常見的單例實現(xiàn)方式有如下:

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

代碼實現(xiàn)

  • 餓漢式
package singleton.hungry;

/**
 * 單例模式:餓漢式實現(xiàn)方式,類在加載的時候就創(chuàng)建單例對象
 */
public class HungrySingleton {
    /**
     * 定義類靜態(tài)變量,類加載的時候就已經(jīng)創(chuàng)建好單例對象
     */
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return INSTANCE;
    }
}

餓漢式在類加載的時候就已經(jīng)將單例對象創(chuàng)建好了,但是如果沒有使用它,一定程度上造成浪費。不過,由于在類加載的時候單例對象就創(chuàng)建好了,因此它是線程安全的。

  • 懶漢式
package singleton.lazy;


/**
 * 單例模式:懶漢式實現(xiàn)方式
 */
public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if (INSTANCE == null){
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

懶漢式是在外部調(diào)用類的方法獲取單例對象時才會創(chuàng)建單例對象,它不會想餓漢式那樣造成浪費。但是,會導(dǎo)致線程安全問題。在高并發(fā)情況下,可能多個線程都調(diào)用了構(gòu)造方法來創(chuàng)建對象。為了解決這個線程并發(fā)問題,通常需要加鎖。

  • 線程安全版懶漢式
package singleton.safeLazy;

/**
 * 單例模式:線程安全的懶漢式實現(xiàn)
 */
public class SafeLazySingleton {

    /**
     * 加volatile關(guān)鍵字,防止重排序
     */
    private static volatile SafeLazySingleton INSTANCE = null;
    
    private SafeLazySingleton(){}
    
    public static SafeLazySingleton getInstance(){
        if(INSTANCE == null){
            synchronized (SafeLazySingleton.class){
                // 雙重校驗
                if(INSTANCE == null){
                    INSTANCE = new SafeLazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

線程安全版的懶漢式實現(xiàn)要點是:1. volatile關(guān)鍵字 2. synchronized鎖 3. 雙重校驗判空
1. volatile關(guān)鍵字可以防止初始化對象時由于編譯器的作用導(dǎo)致指令重排序

一個對象初始化的步驟包括:

  • 給對象實例分配一塊內(nèi)存空間instance = allocate(SafeLazySingleton.class)
  • 針對分配好的內(nèi)存空間的對象實例,執(zhí)行構(gòu)造方法,對這個對象實例進行初始化操作,對各個成員變量賦值,執(zhí)行初始化邏輯。invokeConstructor(instance)
  • 經(jīng)過上面兩個步驟,一個對象實例完成;此時將instance指針指向這塊內(nèi)存空間,賦值給我們引用類型的變量,讓它指向SafeLazySingleton對象的內(nèi)存地址。

以上是正常的步驟,然而在編譯器重排序的作用下,可能真正執(zhí)行的指令順序是 1-> 3-> 2。
所以當(dāng)A線程去創(chuàng)建單例對象實例,進行到3步驟,由于重排序,此時的對象是殘缺的;而此時B線程判斷INSTANCE對象為空,于是進行了某些操作,由于是殘缺對象,因此會出錯。
2. 雙重校驗
第二重校驗是避免A、B兩個線程依次進入了同步代碼塊,重復(fù)創(chuàng)建單例對象。
3. synchronized同步鎖
其實synchronized可以直接加在方法上,這樣就不用雙重校驗了,但是這樣的鎖粒度比較大。

  • 靜態(tài)內(nèi)部類
package singleton.staticClass;

/**
 * 單例模式:靜態(tài)內(nèi)部類
 */
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton(){}

    /**
     * 通過靜態(tài)內(nèi)部類加載時創(chuàng)建單例對象
     */
    private static class Inner{
        final static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }

    /**
     * 第一次調(diào)用此方法時,觸發(fā)靜態(tài)內(nèi)部類加載從而創(chuàng)建單例對象
     * @return
     */
    public static StaticInnerClassSingleton getInstance(){
        return Inner.INSTANCE;
    }
}

此方法利用了JVM類加載的線程安全實現(xiàn)單例。

最后編輯于
?著作權(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)容

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