【Java面試指北】單例模式

大数据王小皮發表於2024-04-10

單執行緒下的單例模式:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton()
        }
        return instance;
    }
}

幾個關鍵點:

  • static 修飾:表名屬於類而不是類物件,不會每生成一個新的類物件都新生成一份。並且可以在不建立類物件的情況下直接呼叫。
  • 為什麼建構函式是private 型別?不然呢,開放了建構函式還怎麼單例。
  • 為什麼不把單例的邏輯放到建構函式中?在 Singleton() 中呼叫 Singleton() 麼,那不是死迴圈了。
  • 類中的單例變數是 private 型別的,不能直接訪問,要透過 getInstance() 來獲取。

多執行緒下的單例模式:

和單執行緒有什麼區別?

  • 需要考慮執行緒安全問題
  • 需要考慮效率問題

方法一:
只需要給 getInstance 方法新增 synchronized 關鍵字即可。

public static synchronized Singleton getInstance() {

問題:每次訪問都要同步,會降低效能。

方法二:
雙重檢查鎖定

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton()
                }
            }
        }
        return instance;
    }
}

關鍵點:

  • 效能最佳化:將 synchronized 放到實際建立時,只有第一次例項未建立才會同步,後續都不會。
  • 為什麼要雙重檢查 instance==null?第一次檢查完,有可能被別的執行緒先建立了。
  • 為什麼 instance 要用 volatile 修飾?
    • 因為 new Singleton() 不是一個單一的操作,會存在指令重排的問題。
    • 1、為 instance 分配記憶體空間。2、初始化 instance。3、將 instance 指向分配的記憶體地址。
    • 如果指令重拍後,變為了 1-3-2,那麼其他執行緒可能會拿到一個還沒初始化的 instance
  • 為什麼有了 volatile 還需要 synchronized
    • 因為 volatile 不保證原子性。

相關文章