首頁技術(shù)文章正文

單例模式的實(shí)現(xiàn)方法匯總

更新時間:2023-01-06 來源:黑馬程序員 瀏覽量:

單例模式(Singleton Pattern)是Java中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實(shí)例化該類的對象。


1.單例模式的結(jié)構(gòu)

單例模式的主要有以下角色:

·單例類。只能創(chuàng)建一個實(shí)例的類

·訪問類。使用單例類


2. 單例模式的實(shí)現(xiàn)

單例設(shè)計模式分類兩種:

餓漢式:類加載就會導(dǎo)致該單實(shí)例對象被創(chuàng)建

懶漢式:類加載不會導(dǎo)致該單實(shí)例對象被創(chuàng)建,而是首次使用該對象時才會創(chuàng)建


2.1 餓漢式-方式1(靜態(tài)變量方式)

/**
 * 餓漢式
 *      靜態(tài)變量創(chuàng)建類的對象
 */
public class Singleton {
    //私有構(gòu)造方法
    private Singleton() {}

    //在成員位置創(chuàng)建該類的對象
    private static Singleton instance = new Singleton();

    //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {
        return instance;
    }
}

說明:

該方式在成員位置聲明Singleton類型的靜態(tài)變量,并創(chuàng)建Singleton類的對象instance。instance對象是隨著類的加載而創(chuàng)建的。如果該對象足夠大的話,而一直沒有使用就會造成內(nèi)存的浪費(fèi)。


2.2 餓漢式-方式2(靜態(tài)代碼塊方式)

/**
 * 惡漢式
 *      在靜態(tài)代碼塊中創(chuàng)建該類對象
 */
public class Singleton {

    //私有構(gòu)造方法
    private Singleton() {}

    //在成員位置創(chuàng)建該類的對象
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {
        return instance;
    }
}

說明:

該方式在成員位置聲明Singleton類型的靜態(tài)變量,而對象的創(chuàng)建是在靜態(tài)代碼塊中,也是對著類的加載而創(chuàng)建。所以和餓漢式的方式1基本上一樣,當(dāng)然該方式也存在內(nèi)存浪費(fèi)問題。


2.3 懶漢式-方式1(線程不安全)

/**
 * 懶漢式
 *  線程不安全
 */
public class Singleton {
    //私有構(gòu)造方法
    private Singleton() {}

    //在成員位置創(chuàng)建該類的對象
    private static Singleton instance;

    //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


說明:

從上面代碼我們可以看出該方式在成員位置聲明Singleton類型的靜態(tài)變量,并沒有進(jìn)行對象的賦值操作,那么什么時候賦值的呢?當(dāng)調(diào)用getInstance()方法獲取Singleton類的對象的時候才創(chuàng)建Singleton類的對象,這樣就實(shí)現(xiàn)了懶加載的效果。但是,如果是多線程環(huán)境,會出現(xiàn)線程安全問題。


2.4 懶漢式-方式2(線程安全)

/**
 * 懶漢式
 *  線程安全
 */
public class Singleton {
    //私有構(gòu)造方法
    private Singleton() {}

    //在成員位置創(chuàng)建該類的對象
    private static Singleton instance;

    //對外提供靜態(tài)方法獲取該對象
    public static synchronized Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

說明:

該方式也實(shí)現(xiàn)了懶加載效果,同時又解決了線程安全問題。但是在getInstance()方法上添加了synchronized關(guān)鍵字,導(dǎo)致該方法的執(zhí)行效果特別低。從上面代碼我們可以看出,其實(shí)就是在初始化instance的時候才會出現(xiàn)線程安全問題,一旦初始化完成就不存在了。


2.5 懶漢式-方式3(雙重檢查鎖)

/**
 * 雙重檢查方式
 */
public class Singleton { 

    //私有構(gòu)造方法
    private Singleton() {}

    private static Singleton instance;

   //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {
		//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)例
        if(instance == null) {
            synchronized (Singleton.class) {
                //搶到鎖之后再次判斷是否為null
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

再來討論一下懶漢模式中加鎖的問題,對于 getInstance() 方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒必讓每個線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的時機(jī)。由此也產(chǎn)生了一種新的實(shí)現(xiàn)模式:雙重檢查鎖模式

雙重檢查鎖模式是一種非常好的單例實(shí)現(xiàn)模式,解決了單例、性能、線程安全問題,上面的雙重檢測鎖模式看上去完美無缺,其實(shí)是存在問題,在多線程的情況下,可能會出現(xiàn)空指針問題,出現(xiàn)問題的原因是JVM在實(shí)例化對象的時候會進(jìn)行優(yōu)化和指令重排序操作。

要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用 volatile 關(guān)鍵字, volatile 關(guān)鍵字可以保證可見性和有序性。

/**
 * 雙重檢查方式
 */
public class Singleton {

    //私有構(gòu)造方法
    private Singleton() {}

    private static volatile Singleton instance;

   //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {
		//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)際
        if(instance == null) {
            synchronized (Singleton.class) {
                //搶到鎖之后再次判斷是否為空
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

小結(jié):

添加 volatile 關(guān)鍵字之后的雙重檢查鎖模式是一種比較好的單例實(shí)現(xiàn)模式,能夠保證在多線程的情況下線程安全也不會有性能問題。


2.6 懶漢式-方式4(靜態(tài)內(nèi)部類方式)

靜態(tài)內(nèi)部類單例模式中實(shí)例由內(nèi)部類創(chuàng)建,由于 JVM 在加載外部類的過程中, 是不會加載靜態(tài)內(nèi)部類的, 只有內(nèi)部類的屬性/方法被調(diào)用時才會被加載, 并初始化其靜態(tài)屬性。靜態(tài)屬性由于被 static 修飾,保證只被實(shí)例化一次,并且嚴(yán)格保證實(shí)例化順序。

/**
 * 靜態(tài)內(nèi)部類方式
 */
public class Singleton {

    //私有構(gòu)造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //對外提供靜態(tài)方法獲取該對象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

說明:

第一次加載Singleton類時不會去初始化INSTANCE,只有第一次調(diào)用getInstance,虛擬機(jī)加載SingletonHolder

并初始化INSTANCE,這樣不僅能確保線程安全,也能保證 Singleton 類的唯一性。

小結(jié):

靜態(tài)內(nèi)部類單例模式是一種優(yōu)秀的單例模式,是開源項(xiàng)目中比較常用的一種單例模式。在沒有加任何鎖的情況下,保證了多線程下的安全,并且沒有任何性能影響和空間的浪費(fèi)。


2.7 枚舉方式

枚舉類實(shí)現(xiàn)單例模式是極力推薦的單例實(shí)現(xiàn)模式,因?yàn)槊杜e類型是線程安全的,并且只會裝載一次,設(shè)計者充分的利用了枚舉的這個特性來實(shí)現(xiàn)單例模式,枚舉的寫法非常簡單,而且枚舉類型是所用單例實(shí)現(xiàn)中唯一一種不會被破壞的單例實(shí)現(xiàn)模式。

<br class="Apple-interchange-newline"><div></div>

/**
 * 枚舉方式
 */
public enum Singleton {
    INSTANCE;
}

說明:

枚舉方式屬于惡漢式方式。

IT培訓(xùn)班

分享到:
在線咨詢 我要報名
和我們在線交談!