【設計模式】實現執行緒安全單例模式的五種方式

gonghr發表於2022-01-27

餓漢式

餓漢式:類載入就會導致該單例項物件被建立

// 問題1:為什麼加 final
// 問題2:如果實現了序列化介面, 還要做什麼來防止反序列化破壞單例
public final class Singleton_hungry implements Serializable {

    // 問題3:為什麼設定為私有? 是否能防止反射建立新的例項?
    private Singleton_hungry(){}

    // 問題4:這樣初始化是否能保證單例物件建立時的執行緒安全?
    private static Singleton_hungry INSTANCE = new Singleton_hungry();

    // 問題5:為什麼提供靜態方法而不是直接將 INSTANCE 設定為 public, 說出你知道的理由
    public static Singleton_hungry getInstance() {
        return INSTANCE;
    }
    public Object readResolve(){  // 防止反射建立新的例項?
        return INSTANCE;
    }
}

  • 問題1:
    避免子類覆蓋父類的一些方法,導致執行緒不安全。

  • 問題2:
    實現 readResolve 方法。當從物件流 ObjectInputStream 中讀取物件時,會檢查物件的類否定義了 readResolve 方法。如果定義了,則呼叫它返回我們想指定的物件(這裡就指定了返回單例物件)。

  • 問題3:防止通過 new 建立物件例項。不能防止反射建立新的例項。

  • 問題4:可以。靜態變數初始化在類載入時進行,由 jvm 進行管理,可以保證執行緒安全。

  • 問題5:通過方法,可以提高擴充性,改進餓漢式轉化為懶漢式、利用泛型特性、增加對單例物件的控制操作。

列舉單例

enum Singleton { 
   INSTANCE; 
}
  • 問題1:列舉單例是如何限制例項個數的
    單例相當於列舉的靜態成員變數,定義幾個就有幾個例項。

  • 問題2:列舉單例在建立時是否有併發問題
    單例相當於列舉的靜態成員變數,類載入時初始化,由 jvm 進行管理,可以保證執行緒安全。

  • 問題3:列舉單例能否被反射破壞單例
    不能

  • 問題4:列舉單例能否被反序列化破壞單例
    列舉實現了 Serializable 介面,可序列化,但不會被反序列破壞單例。

  • 問題5:列舉單例屬於懶漢式還是餓漢式
    餓漢式

  • 問題6:列舉單例如果希望加入一些單例建立時的初始化邏輯該如何做
    列舉允許構造方法

懶漢式

public final class Singleton_lazy {
    private Singleton_lazy(){}
    private static Singleton_lazy INSTANCE = null;
    // 缺點
    public static synchronized Singleton_lazy getInstance() {
        if(INSTANCE != null) {
            return INSTANCE;
        }
        INSTANCE = new Singleton_lazy();
        return INSTANCE;
    }
}
  • synchronized 保證執行緒安全,但鎖粒度較大,效能低。

DCL 懶漢式

public final class Singleton_DCL {

    private Singleton_DCL() {}

    // 問題1:解釋為什麼要加 volatile ?
    private static volatile Singleton_DCL INSTANCE= null;

    // 問題2:對比實現3, 說出這樣做的意義
    public static Singleton_DCL getInstance() {
        if(INSTANCE != null) {
            return INSTANCE;
        }
        synchronized (Singleton_DCL.class) {
          
            // 問題3:為什麼還要在這裡加為空判斷, 之前不是判斷過了嗎
            if(INSTANCE != null) {
                return INSTANCE;
            }
            INSTANCE = new Singleton_DCL();
            return INSTANCE;
        }
    }
}
  • 問題1:避免指令重排序,導致賦值語句先於建構函式執行,得到一個未初始化完畢的物件。

  • 問題2、3:Double Check Lock 機制。同步程式碼塊外部的判斷語句主要用於 INSTANCE 初始化並賦值之後,此時 INSTANCE != null,如果有多個執行緒嘗試獲取單例,可以提前返回,不用執行同步程式碼塊。而同步程式碼塊內部的判斷主要用於第一次初始化時,INSTANCE = null,此時可以有多個執行緒嘗試獲取 INSTANCE,只能有一個執行緒進入同步程式碼塊,其他執行緒在同步程式碼塊外阻塞,該執行緒建立一個單例物件之後,喚醒其他執行緒,再進入同步程式碼塊,發現 INSTANCE != null,則直接返回,不用重新建立單例物件,提高了效率。

靜態內部類懶漢單例

public final class Singleton_LazyHolder {
    private Singleton_LazyHolder(){}

    // 問題1:屬於懶漢式還是餓漢式
    private static class LazyHolder{
        static final Singleton_LazyHolder INSTANCE = new Singleton_LazyHolder();
    }

    // 問題2:在建立時是否有併發問題
    public static Singleton_LazyHolder getInstance() {
        return LazyHolder.INSTANCE;
    }
}
  • 問題1:懶漢式。靜態內部類只有在被方法呼叫的時候才進行初始化,類載入。

  • 問題2:無,類載入由 jvm 進行,執行緒安全。

相關文章