一文帶你搞懂單例設計模式

更多知識,請移步我的小破站:http://hellofriend.top

什麼是單例設計模式?

單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。

通過單例模式可以保證系統中,應用該模式的類只有一個實例。即一個類只有一個對象實例。

在Java語言中,單例帶來了兩大好處:

  1. 對於頻繁使用的對象(數據源、Session工廠),可以省略創建對象所花費的時間,這對於重量級的對象而言,是非常可觀的一筆系統開銷。
  2. 由於new操作的次數減少,因而對系統內存的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。

具體實現

需要:
(1)將構造方法私有化,使其不能在類的外部通過new關鍵字實例化該類對象。
(2)在該類內部產生一個唯一的實例化對象,並且將其封裝爲 private static 類型。
(3)定義一個靜態方法返回這個唯一對象。

實現一:餓漢式 / 靜態常量

  • 立即加載就是使用類的時候已經將對象創建完畢(不管以後會不會使用到該實例化對象,先創建了)。常見的實現辦法就是直接 new 實例化。

代碼如下:

public class HungrySingletonStaticInstance {
    public static void main(String[] args) {
        System.out.println("======HungrySingletonStaticInstance======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // 將自身實例化對象設置爲一個屬性,並用static、final修飾
    private final static Singleton instance = new Singleton();

    // 構造方法私有化
    private Singleton() {}

    // 提供靜態方法返回該實例
    public static Singleton getInstance() {
        return instance;
    }
}

“餓漢模式” 的優缺點分析:

  • 優點:實現起來簡單,沒有多線程同步問題。
  • 缺點:當類 Singleton 被加載的時候,會初始化 staticinstance,靜態變量被創建並分配內存空間,從這以後,這個 staticinstance對象便一直佔着這段內存,可能造成內存浪費(即便你還沒有用到這個實例)。當類被卸載時,靜態變量被摧毀,並釋放所佔有內存,在某些特定條件下會耗費內存。
  • 補充:如果方法內有其他 static 方法,調用該方法此類也加載初始化。

實現二:餓漢式 / 靜態代碼塊

public class HungrySingletonStaticBlock {
    public static void main(String[] args) {
        System.out.println("======HungrySingletonStaticBlock======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
  • 如果方法內有其他static方法,調用該方法此類也加載初始化。

實現三:懶漢式 / 線程不安全

  • 延遲加載就是調用 getInstance() 方法時實例才被創建(先不急着實例化出對象,等要用的時候才創建出來)。常見的實現方法就是在getInstance() 方法中進行 new 實例化。
public class LazyLoadingSingletonThreadUnSafe {
    public static void main(String[] args) {
        System.out.println("======LazySingletonThreadUnsafe======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // 將自身實例化對象設置爲一個屬性,並用static修飾
    private static Singleton instance;

    // 構造方法私有化
    private Singleton() {}

    // 靜態方法返回該實例
    public static Singleton getInstance() {
        if (instance == null) {
            //TODO 線程在這裏被阻塞,則此時對象沒有被創建,UnSafe
            instance = new Singleton();
        }
        return instance;
    }
}

“懶漢模式” 的優缺點分析:

  • 優點:實現起來比較簡單,當類 Singleton 被加載的時候,靜態變量 staticinstance 未被創建並分配內存空間,當getInstance() 方法第一次被調用時,初始化instance變量,並分配內存,因此在某些特定條件下會節約了內存。
  • 缺點:在多線程環境中,這種實完現方法是錯誤的,不能保證單例的狀態。
  • 補充:如果方法內有其他static方法,調用該方法此類不會加載初始化。

實現四:懶漢式 / 線程安全 Sync

  • 靜態方法返回該實例,加 Synchronized 關鍵字實現同步。
public class LazyLoadingSingletonSync {
    public static void main(String[] args) {
        System.out.println("======LazyLoadingSingletonSync======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // 將自身實例化對象設置爲一個屬性,並用static修飾
    private static Singleton instance;

    // 構造方法私有化
    private Singleton() {}

    // 靜態方法返回該實例
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

線程安全的“懶漢模式”(加鎖)的優缺點:

  • 優點:在多線程情形下,保證了“懶漢模式”的線程安全。
  • 缺點:在多線程情形下,synchronized方法通常效率低,顯然這不是最佳的實現方案。
  • 補充:如果方法內有其他static方法,調用該方法此類不會加載初始化。

實現五:DCL雙重檢查鎖定機制

(DCL:Double Checked Locking)

public class LazyLoadingSingletonDCL {
    public static void main(String[] args) {
        System.out.println("======LazyLoadingSingletonDCL======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // 將自身實例化對象設置爲一個屬性,並用 volatile、static 修飾
    private volatile static Singleton instance;

    // 構造方法私有化
    private Singleton() {}

    // 靜態方法返回該實例
    public static Singleton getInstance() {
        // 第一次檢查instance是否被實例化出來
        if (instance == null) {
            synchronized (Singleton.class) {
                // 某個線程取得了類鎖,實例化對象前第二次檢查 instance 是否已經被實例化
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 該方法是單例模式推薦的實現方式。內存佔用率高,效率高,線程安全,多線程操作原子性。
  • 如果方法內有其他 static 方法,調用該方法此類不會加載初始化。

實現六:靜態內部類

public class StaticInternalSingleton {
    public static void main(String[] args) {
        System.out.println("======StaticInternalSingleton======");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}


class Singleton {

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        //加載 Singleton 類時並不會加載內部類
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 第一次加載 Singleton 類時並不會加載內部類初始化 Instance,只有第一次調用 getInstance 方法時虛擬機加載 SingletonHolder 並初始化 Instance ,這樣不僅能確保線程安全也能保證Singleton類的唯一性,所以推薦使用靜態內部類單例模式。

實現七:枚舉單例

該方法藉助 JDK 1.5 中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。

public class EnumSingleton {
    public static void main(String[] args) {
        System.out.println("======EnumSingleton======");
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());

        instance1.method();
        instance2.method();
    }
}

enum Singleton {
    INSTANCE;

    public void method() {
        System.out.println("EnumSingleton");
    }
}

JDK 中單例模式的應用之 Runtime 類

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
	//TODO 私有化構造器
    private Runtime() {}

很顯然,JDK 中的 Runtime 類使用的是本文的 實現一:餓漢式 / 靜態常量來實現的,由於 Runtime 類在程序運行中一定會使用到,所以並不會帶來餓漢式的空間浪費問題。

小總結

1)單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統的性能。

2)當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new。

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