2020-12-20

土著神的頂點發表於2020-12-20

單例模式

1、概念

(1)什麼是單例模式?

​ 單例模式,是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個例項,並提供一個訪問它的全域性訪問點。即一個類只有一個物件例項。

​ 適用性:當類只能有一個例項而且客戶可以從一個眾所周知的訪問點訪問它時。當這個唯一例項應該是通過子類化可擴充套件的,並且客戶應該無需更改程式碼就能使用一個擴充套件的例項時。

(2)單例模式實現原理

(1)將構造方法私有化,使其不能在類的外部通過new關鍵字例項化該類物件。

(2)在該類內部產生一個唯一的例項化物件,並且將其封裝為private static型別。

(3)定義一個靜態方法返回這個唯一物件。

2、單例模式的實現方式

(1):餓漢式

​ 就是使用類的時候已經將物件建立完畢(不管以後會不會使用到該例項化物件,先建立了再說。很著急的樣子,故又被稱為“餓漢模式”)。常見的實現辦法就是直接new例項化。

package com.example.designmode.singleton;

/**
 * 餓漢式 :就是使用類的時候已經將物件建立完畢(不管以後會不會使用到該例項化物件,先建立了再說。很著急的樣子,故又被稱為“餓漢模式”),
 * 常見的實現辦法就是直接new例項化。
 * 優點:實現起來簡單,沒有多執行緒同步問題。
 * 缺點:當類SingletonTest被載入的時候,會初始化static的instance,靜態變數被建立並分配記憶體空間,
 * 從這以後,這個static的instance物件便一直佔著這段記憶體(即便你還沒有用到這個例項),
 * 當類被解除安裝時,靜態變數被摧毀,並釋放所佔有的記憶體,因此在某些特定條件下會耗費記憶體。
 */
public class EagerSingleton {
    // jvm保證在任何執行緒訪問eagerSingleton靜態變數之前一定先建立了此例項
    private static EagerSingleton eagerSingleton = new EagerSingleton();

    // 私有的預設構造子,保證外界無法直接例項化
    private EagerSingleton() {

    }

    // 提供全域性訪問點獲取唯一的例項
    public static EagerSingleton getInstance() {
        return eagerSingleton;
    }
}

總結:

優點:實現起來簡單,沒有多執行緒同步問題。
缺點:當類SingletonTest被載入的時候,會初始化static的instance,靜態變數被建立並分配記憶體空間,從這以後,這個static的instance物件便一直佔著這段記憶體(即便你還沒有用到這個例項),當類被解除安裝時,靜態變數被摧毀,並釋放所佔有的記憶體,因此在某些特定條件下會耗費記憶體。

(2):懶漢式

​ 延遲載入(懶漢式)就是呼叫get()方法時例項才被建立(先不急著例項化出物件,等要用的時候才給你建立出來。不著急,故又稱為“懶漢模式”)。常見的實現方法就是在get方法中進行new例項化。

package com.example.designmode.singleton;

/**
 * 延遲載入(懶漢式)就是呼叫get()方法時例項才被建立(先不急著例項化出物件,等要用的時候才給你建立出來。不著急,故又稱為“懶漢模式”),
 * 常見的實現方法就是在get方法中進行new例項化。
 *
 */
public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

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

總結:

優點:實現起來比較簡單,當類SingletonTest被載入的時候,靜態變數static的instance未被建立並分配記憶體空間,當getInstance方法第一次被呼叫時,初始化instance變數,並分配記憶體,因此在某些特定條件下會節約了記憶體。

缺點:在多執行緒環境中,這種實現方法是完全錯誤的,根本不能保證單例的狀態。

(3):執行緒安全的“懶漢模式”

package com.example.designmode.singleton;

/**
 * 執行緒安全的“懶漢模式”
 *
 */
public class SynchronizedLazySingleton {

    private static SynchronizedLazySingleton lazySingleton = null;

    private SynchronizedLazySingleton() {

    }

    public static synchronized SynchronizedLazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new SynchronizedLazySingleton();
        }
        return lazySingleton;
    }
}

總結:

優點:在多執行緒情形下,保證了“懶漢模式”的執行緒安全。

缺點:眾所周知在多執行緒情形下,synchronized方法通常效率低,顯然這不是最佳的實現方案。

(4):DCL雙檢查鎖機制(DCL:double checked locking)

​ 算是單例模式的最佳實現方式之一。記憶體佔用率高,效率高,執行緒安全,多執行緒操作原子性。

package com.example.designmode.singleton;

/**
 * DCL雙檢查鎖機制(DCL:double checked locking)
 *
 */
public class DoubleCheckedLockingLazySingleton {

    private static DoubleCheckedLockingLazySingleton lazySingleton = null;

    private DoubleCheckedLockingLazySingleton() {

    }

    public static DoubleCheckedLockingLazySingleton getInstance() {
        // 第一次檢查lazySingleton是否被例項化出來,如果沒有進入if塊
        if (lazySingleton == null) {
            synchronized (DoubleCheckedLockingLazySingleton.class) {
                // 某個執行緒取得了類鎖,例項化物件前第二次檢查lazySingleton是否已經被例項化出來,如果沒有,才最終例項出物件
                if (lazySingleton == null) {
                    lazySingleton = new DoubleCheckedLockingLazySingleton();
                }
            }

        }
        return lazySingleton;
    }
}

(5):靜態內部類懶漢式

​ 原理:類裝載時,靜態內部類不會被裝載。呼叫公共方法時,載入內部類,載入只會有一次,所以執行緒安全。(在類進行初始化時,別的執行緒是無法進入的,提供了安全性保證)

package com.example.designmode.singleton;

/**
 * 靜態內部類懶漢式:當任何一個執行緒第一次呼叫getInstance時,都會使StaticInnerClasssLazySingletonInner被載入和被初始化,此時
 * 靜態初始化器將執行lazySingleton的初始化操作(被呼叫時才初始化)。初始化靜態資料時,java提供了執行緒安全性保證,所以不需要任何同步。
 *
 * 注:類裝載時,靜態內部類不會被裝載。呼叫公共方法時,載入內部類,載入只會有一次,所以執行緒安全。【在類進行初始化時,別的執行緒是無法進入的。 】
 */
public class StaticInnerClasssLazySingleton {

    private StaticInnerClasssLazySingleton() {

    }

    private static class StaticInnerClasssLazySingletonInner {
        // 利用JVM裡靜態成員只被載入一次的特點來保證只有一個例項物件
        private static final StaticInnerClasssLazySingleton lazySingleton = new StaticInnerClasssLazySingleton();
    }

    public static StaticInnerClasssLazySingleton getInstance() {
        return StaticInnerClasssLazySingletonInner.lazySingleton;
    }
}

總結:

優點:避免了執行緒不安全,利用靜態內部類特點實現延遲載入,效率高。
缺點:暫無。

(6):列舉式

​ 保證單例的方式: 首先,在列舉中我們明確了構造方法限制為私有,在我們訪問列舉例項時會執行構造方法,同時每個列舉例項都是static final型別的,也就表明只能被例項化一次。在呼叫構造方法時,我們的單例被例項化。也就是說,因為Enum中的例項被保證只會被例項化一次,所以我們的INSTANCE也被保證例項化一次。

package com.example.designmode.singleton;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public enum EnumSingleton {
    INSTANCE;

    public void test() {
        log.info("測試列舉單例類的用法");
    }
    
    public static void main(String[] args) {
        System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
        System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
        System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
        System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
        System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));

        EnumSingleton.INSTANCE.test();
    }
}
優點:簡單、防止多次例項化,可以防止序列化破壞和反射攻擊、預設就是執行緒安全的。
借用 《Effective Java》一書中的話: 單元素的列舉型別已經成為實現Singleton的最佳方法。

3、總結

​ 列舉式式最簡單最優秀的單例寫法,可以防止反射工具(詳細參考《如何防止單例模式被JAVA反射攻擊》)和序列化破壞(詳細參考《JAVA序列化 》)。建議程式設計時採用靜態內部類懶漢式(不能防止反射和序列化破壞),當然寫成列舉式就更好啦。

參考部落格:

​ https://www.cnblogs.com/binaway/p/8889184.html

​ https://blog.csdn.net/u013256816/article/details/50966882