java 實現線程安全的單例模式


一、平時使用的軟件中,例如 回收站、線程池、文件系統等,都只有一個實例,這些都是單例模式的典型應用。

  單例模式:確保某個類只有一個實例,並提供一個全局訪問點來訪問這個實例。

  單例模式有三個要點:

    1. 某個類只能有一個實例

    2. 必須自行創建這個實例

    3. 必須自行向整個系統提供這個實例。

  以上三個要點提示着我們的代碼編寫需要注意,構造函數必須私有,否則在其他類中便可以調用構造函數創建實例,難以保證實例的唯一性。

二、單例模式分為餓漢模式和懶漢模式

//餓漢模式:(線程安全)
public class Singleton1 {
    // 靜態私有成員變量
    private static Singleton1 instance = new Singleton1();
    // 私有構造函數
    private Singleton1() {
    }
  // 靜態公有工廠方法,返回唯一實例
    public static Singleton1 getInstance() {
        return instance;
    }
}

 

// 懶漢模式:(線程不安全,需要通過雙重檢查鎖定機制控制)
public class Singleton2 {
   // 靜態私有成員變量
    private static Singleton2 instance = null;
  // 私有構造函數
    private Singleton2() {
    }
  // 靜態公有工廠方法,判斷成員變量是否為空,不為空則實例化
    public static Singleton2 getInstance() {
        if(instance == null)
            instance = new Singleton2();
        return instance;
    }
}

  優缺點:

   餓漢模式不需要考慮線程安全問題,調用速度和訪問速度優於懶漢模式,但是由於它不管是否被調用都會提前創建類的實例,所以資源利用效率比較低,系統加載時間比較長。

懶漢模式實現了延遲加載,但是需要克服多個線程同時訪問的問題,需要通過雙重檢查鎖定機制進行控制,導致系統性能受到一定影響。

三、下面兩個方法實現懶漢模式的線程安全。

  為什么會線程不安全?

   假設有兩個線程 A B,其中 A 執行到檢查方法,即 if(instance == null) 前,實例都沒有被創建,那么 A 會得到 ture 的結果,但是此時調度算法選擇 B 線程運行,那么當 B 執行 到 if(instance == null) 時得到的也是 true,那這就很尷尬了,兩個線程都會執行 instance = new Singleton2(); 從而創建了兩個實例。

  1.雙重檢查鎖定機制。

  為了避免以上這種尷尬的情況,需要將這兩行代碼加上同步鎖。但這還不夠完美,每次調用函數得到實例都要試圖加上一個同步鎖(類鎖),而加鎖是一個非常耗時的操作,沒有必要的情況下應該盡量避免。基於這種想法,我們可以在加鎖前再次判斷實例是否為空。這就是雙重檢查鎖定機制。

  還有一點,定義 instance 變量時需要使用 volatile 進行修飾,因為需要保證 instance 變量發生修改后可以及時將結果刷新到主內存中,對其他線程可見。

 

public class Singleton3 {
  // 私有靜態成員變量
    private static volatile Singleton3 instance = null;
  // 私有構造函數
    private Singleton3() {
    }
  // 共有靜態工廠方法
    public static Singleton3 getInstance() {
  // 判斷 instance 是否為空,為空->加鎖,創建實例(為了進程安全,再次判斷),不為空->返回實例
        if(instance == null) {
            synchronized (Singleton3.class) {
                if(instance == null)
                    instance = new Singleton3();
            }
        }
        return instance;
    }
}

 

 

 

  2. 使用靜態內部類創建實例 。JAVA 語言中最好的實現方法)

public class Singleton4 {
  // 私有構造函數
    private Singleton4() {
    }
  // 靜態內部類
    private static class HolderClass{
        private static final Singleton4 instance = new Singleton4();
    }
  // 靜態公有工廠方法,返回內部類中創建的實例
    public static Singleton4 getInstance() {
        return HolderClass.instance;
    }
}

  加載 Singleton4 類的過程中,會為類變量在方法區中分配內存空間並初始化,但是,並不會加載靜態內部類 HolderClass

  當調用 Singleton4.getInstance() ,執行 return HolderClass.instance 語句時,HolderClass 類才會被加載,instance 對象才會被初始化

  上面的解釋說明使用靜態內部類創建實例是 懶加載 的; HolderClass 只會加載一次,保證了 instance 是單例的;類加載過程是線程安全的,保證 instance 初始化的過程是線程安全的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM