設計模式系列之一——單例模式幾種實現方式

前言:要改變命運,首先要改變自己。

一、概述

設計模式代表了最佳的實踐,是一套被反覆使用,多數人知曉,經過分類編目,代碼設計經驗的總結。使用設計模式是爲了重用代碼,讓代碼更容易被人理解,保證代碼可靠性。設計模式提供了軟件開發過程中面臨的一些問題的最佳解決方案,非常重要,主要分爲創建型模式、結構型模式、創建型模式和J2EE 模式。

單例模式是設計模式中最簡單的模式之一。這種類型的設計模式屬於創建型設計模式,它提供了一種最佳創建對象的方式。所謂單例,就是保證整個程序有且僅有一個實例,負責創建自己的對象,同時保證只有一個對象被創建,向整個系統提供這個實例。這個類提供了一種訪問其唯一的對象方式,可以直接訪問,不需要實例化該類的對象。

注意:單例類只有一個實例,必須自己創建自己的唯一實例,給其他所有實例提供這個實例。

特點:

  • 1.構造函數私有
  • 2.持有自己類型的屬性
  • 3.對外提供獲取實例的靜態方法

優缺點:

  • 優點:1.在內存中只有一個實例,節省內存空間;避免頻繁創建銷燬對象,提高性能;
  •            2.避免對共享資源的多種佔用,簡化訪問,爲全局提供一個訪問點。
  • 缺點:1.不適用於變化頻繁的對象;
  •            2.沒有接口,不能繼承,與單一職責原則衝突,只關心內部邏輯,而不關心外部變化。

二、實例

單例模式有多種實現方式,如懶漢式,餓漢式,雙重校驗模式,靜態內部類模式,枚舉等。

1.懶漢模式

懶漢模式,顧名思義,比較“懶”,在需要用到的時候檢查實例,有則直接返回,沒有則纔去創建,有線程安全和線程不安全兩種寫方法。

非線程安全:

//懶漢單例模式:非線程安全
public class LazySingleton {
    //唯一的實例
    private static LazySingleton mInstance;

    //私有構造方法,保證自己纔可以創建實例
    private LazySingleton() {}

    //靜態方法獲取唯一可用的對象
    public static LazySingleton getInstance() {
        if (mInstance == null) {
            mInstance = new LazySingleton();
        }
        return mInstance;
    }
}

這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程,線程不安全的,因爲沒有加鎖synchronized,嚴格意義上來說它不屬於單例,這種方式lazy loading很明顯,不要求線程安全,在多線程不能正常工作。

線程安全:

//懶漢模式:線程安全
public class LazySingleton {
    //唯一的實例
    private static LazySingleton mInstance;

    //私有構造方法,保證自己纔可以創建實例
    private LazySingleton() {}

    //靜態方法獲取唯一可用的對象,synchronized加鎖保證線程安全
    public static synchronized LazySingleton getInstance() {
        if (mInstance == null) {//保證單例的唯一性,在沒有的時候纔去創建
            mInstance = new LazySingleton();
        }
        return mInstance;
    }
}

加鎖 synchronized這種方式很好具備lazy loading(懶加載),線程安全,能都在多線程中很好的工作,但是效率比較低,絕大部分情況下不需要同步。雙重鎖模式更高效,下面講解到。

2.餓漢模式

餓漢式,顧名思義,比較“餓”,在實例化的時候已經創建了,不管你有沒有用到,都先建好實例再說,好處是沒有加鎖,效率較高,沒有線程安全問題,壞處是容易產生垃圾對象,浪費內存。

//餓漢單例模式
public class HungerSingleton {
    //唯一的實例,實例化的時候直接創建
    private static HungerSingleton mInstance = new HungerSingleton();

    //私有構造方法,保證自己纔可以創建實例
    private HungerSingleton() {}

    //靜態方法獲取唯一可用的對象
    public static HungerSingleton getInstance() {
        return mInstance;
    }
}

這種方式比較常用,但是極易產生垃圾對象,線程安全,沒有加鎖,效率比較高,就是類加載時就初始化了,浪費內存。它基於classloader機制避免了多線程問題,不過,instance在類加載時就實例化,雖然導致類裝載的方法有很多中,在單例模式中大多數都是調用getInstance(),但是也不能確定有其他的方法(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然是沒有到達lazy loading的效果。

3.雙重鎖模式

雙重檢鎖是結合了懶漢式和餓漢式的優缺點整合而成,安全且在多線程情況下保證高性能。

//雙檢鎖單例模式
public class Singleton {
    //唯一的實例
    private static Singleton mInstance;

    //私有構造方法,保證自己纔可以創建實例
    private Singleton() {}

    //靜態方法獲取唯一可用的對象
    public static Singleton getInstance() {
        if (mInstance == null) {//爲了不用每次獲取對象都強制加鎖,提升效率,判斷不存在再加鎖
            synchronized (Singleton.class) {//加鎖,爲了線程安全
                if (mInstance == null) {//保證單例的唯一性,在沒有的時候纔去創建
                    mInstance = new Singleton();
                }
            }
        }
        return mInstance;
    }    
}

這種方式採取雙鎖機制,特點是在synchronized內外都加上了一層if條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省內存空間。

4.靜態內部類模式

靜態內部類單例模式其實是餓漢式的變種,利用jvm加載來保證線程安全,並且實現了懶加載,在需要用到的時候纔去創建。

//靜態內部類單例模式
public class StaticSingleton {
    //私有構造方法,保證自己纔可以創建實例
    private StaticSingleton() {
    }

    //靜態成員式內部類,該內部類實例與外部類實例沒有綁定的關係,而且只有被調用時纔會裝載,從而實現延遲加載
    private static class innerSingleton {
        //靜態初始化器,由jvm來保證線程安全
        private static final StaticSingleton instance = new StaticSingleton();
    }

    //靜態方法獲取唯一可用的對象
    public static StaticSingleton getInstance() {
        return innerSingleton.instance;
    }
}

static修飾的靜態內部類,相當於外部類的靜態成員變量,不與外部類實例產生依賴,靜態內部類只可以應用外部類的靜態成員和靜態方法,只有在第一次使用到靜態內部類時纔會被裝載。所以上述的實例instance在使用到時才被初始化。這種方式能到達雙檢鎖一樣的功效,但是實現更簡單,對於靜態域使用初始化,應該使用這種方式,而不是雙檢鎖方式,這種使用於靜態域的情況,雙檢鎖方式可以在實例域延遲初始化使用。

靜態內部類單例模式與餓漢式比較:

靜態內部類單例模式同樣利用了classloader來保證初始化instance只有一個線程,它與餓漢式不同的是,餓漢式只要Singleton類被裝載,instance就會被初始化,沒有達到 lazy loading的效果,而靜態內部類單例模式是Singleton類被裝載了,但是instance不一樣被初始化,因爲靜態內部類innerSingleton沒有被主動使用,而使用通過getInstance()方法顯示去調用,纔會轉載innerSingleton類,從而初始化instance。如果實例化instance很耗資源,所以想讓它延遲加載,不希望它在類實例化時就被初始化,因爲不確定Singleton類還可能在其他地方被主動使用從而被加載,這時候初始化intance很不合適,這種方式比餓漢式就很合理了。

5.枚舉

這種方式沒有被廣泛使用,但是這是單例模式的最佳方法,它更簡潔,避免多線程同步問題,自動支持序列化機制,絕對防止多次實例化。

//枚舉:這種方式沒有被廣泛使用,但是這是單例模式的最佳方法,它更簡潔,避免多線程同步問題,自動支持序列化機制,絕對房子多次實例化。
public enum EnumSingleton {
    INSTANCE;
    public void getInstance(){
    }
}

它在JDK1.5之後纔出現Enum,比較少用到。

我們來總結一下這幾中的用法:

模式 說明 特點
單例模式:
保證整個程序有且僅有一個實例
懶漢式

需要用到纔去檢查實例,有則直接返回,無則直接創建

Lazy初始化,非線程安全,效率高,沒有加鎖
Lazy初始化,線程安全,效率低,加鎖
餓漢式 在類初始化的時候已經創建實例 非Lazy初始化,線程安全,效率高,不加鎖
雙檢鎖模式 採取雙鎖機制,在synchronized內外都加上了一層if條件判斷 Lazy初始化,線程安全,效率高,加鎖
靜態內部類模式 餓漢式的變種,static修飾的靜態內部類,利用jvm加載來保證線程安全,並且實現了懶加載,需要用到的時纔去創建實例 Lazy初始化,線程安全,效率高,不加鎖
枚舉 特殊數據類型 簡潔,避免多線程同步問題,自動支持列化機制,不常用

不建議使用第1種懶漢式,建議使用第2種餓漢式。只有在明確實現lazy loading效果時,纔會使用第4種靜態單例模式,如果涉及到反序列化創建對象時,可以嘗試使用第5種枚舉方式,如果有特殊需求,可以使用第3種雙檢鎖模式。

至此,本文結束!

 

源碼地址:https://github.com/FollowExcellence/JavaDesignMode

請尊重原創者版權,轉載請標明出處:https://blog.csdn.net/m0_37796683/article/details/103203266 謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章