Java 中 Holder 方式 和 枚舉方式 實現單例模式

 Holder 方式 和 枚舉方式 可以線程安全的實現單例模式。

 Holder 方式 藉助於類加載 的初始化階段<clinit>() 方法是同步方法。

枚舉方式 是因爲枚舉類型不能繼承且只能被實例化一次。

1. Holder 方式實現單例模式

類加載 的初始化階段<clinit>() 方法是同步方法,在類中設置一個靜態類,將唯一實例放在靜態類中,第一次訪問實例的時候內部類初始化,即初始化唯一實例。代碼如下所示:

public final class HolderSingleton {
    // 實例變量
    private String value;

    private HolderSingleton() {
        System.out.println("初始化");
    }

    // 靜態內部類持有實例,並可直接被初始化
    private static class Holder {
        private static HolderSingleton instance = new HolderSingleton();
    }

    public static HolderSingleton getInstance() {
        return Holder.instance;
    }

    public static void main(String[] args) {
        HolderSingleton holderSingleton1 = HolderSingleton.getInstance();
        HolderSingleton holderSingleton2 = HolderSingleton.getInstance();
        System.out.println(holderSingleton1 == holderSingleton2);
    }
}

測試截圖如下所示:

2. 枚舉方式實現單例模式

枚舉類型不能繼承且只能被實例化一次,枚舉方式實現單例模式如下所示。

package Singleton;

public enum EnumSingleton {
    INSTANCE;

    private String value;

    EnumSingleton() {
        System.out.println("初始化");
    }

//    public static void method() {
//
//    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        //EnumSingleton.method();
        EnumSingleton enumSingleton1 = EnumSingleton.getInstance();
        EnumSingleton enumSingleton2 = EnumSingleton.getInstance();
        System.out.println(enumSingleton1 == enumSingleton2);
    }
}

測試運行截圖:

調用靜態方法就會導致唯一實例初始化,測試代碼如下所示。

package Singleton;

public enum EnumSingleton {
    INSTANCE;

    private String value;

    EnumSingleton() {
        System.out.println("初始化");
    }

    public static void method() {

    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        EnumSingleton.method();
    }
}

虛擬機規範有且只有下面5種情況立即對類進行“初始化”。

(1) 使用 new 關鍵字 ;Get static,Put static 即設置或訪問靜態變量 (類的靜態字段 被 final 修飾,加入常量池的除外);Invoke static 調用靜態方法 。

(2)使用 java.lang.reflect 包的方法對類進行反射的時候。

(3)初始一個類,發現父類沒有進行初始化,先初始化父類。

(4)虛擬機啓動時, 用戶需要制定一個要執行的主類,先初始化主類。

(5)Java 的動態語言支持時,如果一個 java.lang.invoke.MethodHandle 實例最後的解析結果 REF_getStatic、REF_setStatic、REF_invokeStatic 方法的句柄,並且該句柄的方法所對應類沒有進行過初始化,則需要先觸發其初始化。

參考文獻

  • 深入理解Java虛擬機:JVM高級特性與最佳實踐 / 周志明著. —— 2 版 . —— 北京:機械工業出版社,2013.6 (2019.1重印)
  • Java高併發編程詳解:多線程與架構設計 / 汪文君著——北京:機械工業出版社,2018.5
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章