首頁  >  文章  >  Java  >  不要讓你的單身人士崩潰!以下是如何在 Java 中使其線程安全

不要讓你的單身人士崩潰!以下是如何在 Java 中使其線程安全

Patricia Arquette
Patricia Arquette原創
2024-11-02 09:55:02277瀏覽

Don’t Let Your Singleton Break! Here’s How to Make It % Thread-Safe in Java

在這篇文章中,我們將探索在Java 中實作執行緒安全單例的幾種方法,包括熱切初始化雙重檢查鎖定 內部靜態類別 方法。我們也將討論為什麼 Final 關鍵字有利於確保單例的完整性。

為什麼要使用單例?

當您在整個應用程式中恰好需要類別的一個實例時,單例非常有用。常見用例包括管理共享資源,例如日誌記錄、配置或連線池。單例確保存取一個類別的多個請求共享同一個實例,而不是建立新實例。

1. 熱切初始化:最簡單的單例

急切初始化模式在類別載入時建立單例實例。這很簡單並且確保了線程安全,因為實例是在 JVM 載入類別時建立的。

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

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

熱切初始化的優點和缺點

優點

  • 預設簡單且執行緒安全,因為 JVM 保證類別初始化是執行緒安全的。
  • 無需同步或額外的複雜性。

缺點

  • 無論是否使用實例都會創建,如果永遠不需要單例,這可能會導致資源浪費。

何時使用它:當單例類別是輕量級的並且您確定它將在應用程式運行時使用它時,熱切初始化是最好的。


2.帶有雙重檢查鎖定的延遲初始化

如果您想要延遲建立單例直到需要時(稱為延遲初始化),雙重檢查鎖定提供了一個執行緒安全的解決方案。它使用最少的同步並確保僅在第一次存取實例時才建立實例。

public final class Singleton {  // Marked as final to prevent subclassing

    // volatile ensures visibility and prevents instruction reordering
    private static volatile Singleton instance;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {               // First check (no locking)
            synchronized (Singleton.class) {   // Locking
                if (instance == null) {        // Second check (with locking)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

為什麼雙重檢查鎖定有效

  1. 第一次檢查:同步區塊之外的 if (instance == null) 檢查允許我們避免每次呼叫 getInstance() 時鎖定。這透過繞過初始化後未來調用的同步塊來提高效能。

  2. 同步區塊:一旦實例為空,進入同步區塊可確保只有一個執行緒建立 Singleton 實例。到達這一點的其他線程必須等待,以防止競爭條件。

  3. 第二次檢查:在同步區塊內,我們再次檢查實例以確保當前執行緒等待時沒有其他執行緒初始化它。此雙重檢查可確保僅建立一個 Singleton 實例。

為什麼需要易失性

可變關鍵字在雙重檢查鎖定模式中至關重要,以防止指令重新排序。如果沒有它,語句instance = new Singleton();在完全初始化之前,其他執行緒可能會顯得已完成,從而導致傳回部分構造的實例。易失性保證一旦實例非空,它就被完全建構並且對所有執行緒可見。

為什麼 Final 是良好實踐

這裡使用final關鍵字來防止子類化。將 Singleton 類別標記為 Final 有兩個主要好處:

  1. 防止子類化:將類別設為最終類,我們可以防止其他類別擴展它。這確保了 Singleton 類別只能存在一個實例,因為子類化可能會導致額外的實例,從而破壞單例模式。

  2. 訊號不變性:final 明確地向其他開發人員指示單例類別是不可變的,不應擴展。這使得程式碼更容易理解和維護。

簡而言之,final 增強了單例的完整性,並有助於避免子類化所帶來的意外行為。

雙重檢查鎖定的優點和缺點

優點

  • 延遲初始化透過延遲建立直到需要時來節省資源。
  • 由於雙重檢查而導致的同步開銷最小。

缺點

  • 稍微複雜一點,為了安全需要 volatility。
  • 對於更簡單的單例需求(在急切初始化就足夠的情況下)可能有點過分了。

何時使用它:當單例類別是資源密集且可能不總是需要時,或者當需要考慮多執行緒環境中的效能時,此模式非常有用。


3. 內部靜態類別:更乾淨的延遲初始化替代方案

延遲初始化的另一種執行緒安全方法是內部靜態類別模式。這利用了 Java 的類別載入機制,僅在需要時才初始化單例實例,無需明確同步。

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

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

內部靜態類別如何運作

在此模式中,SingletonHelper 類別僅在第一次呼叫 getInstance() 時載入。這會觸發 INSTANCE 的初始化,確保延遲載入而不需要同步區塊。

內部靜態類別的優點和缺點

優點

  • 線程安全,無需易失性或同步塊。
  • 簡單乾淨,設計上採用延遲初始化。

缺點

  • 對於不熟悉 Java 類別載入機制的新開發人員來說稍微不太直觀。

何時使用它:當您希望使用乾淨、可維護的程式碼進行延遲初始化時,請使用內部靜態類別模式。由於簡單性和線程安全性,這通常是首選。


總結

我們研究了在 Java 中實作執行緒安全單例的三種流行方法:

  1. 急切初始化:最適合單例是輕量級的並且將始終使用的簡單情況。
  2. 雙重檢查鎖定:非常適合效能敏感、需要延遲初始化的多執行緒環境。
  3. 內部靜態類別:使用 Java 類別載入行為進行延遲初始化的乾淨且執行緒安全的方法。

每種方法都有其優點並適合不同的場景。在您自己的專案中嘗試一下,看看哪一個最適合您!如果您有首選方法或有任何問題,請在評論中告訴我。

編碼愉快! ??‍??‍?

以上是不要讓你的單身人士崩潰!以下是如何在 Java 中使其線程安全的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn