1. 程式人生 > >【設計模式】單例模式(Singleton)

【設計模式】單例模式(Singleton)

思想

保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

類圖

Singleton 類稱為單例類,該類的建構函式是 Private 的,這是為了禁止從 Singleton 類的外部呼叫建構函式,這就堵死了外界利用 new 建立此類的可能。通過 getInstance 方法獲得本類例項的唯一全域性訪問點。


實現

1、餓漢式 - 執行緒安全 [可用]

當類被載入時,靜態變數 instance 會被初始化,此時類的私有函式會被呼叫,單例類的唯一例項將被建立。

class Singleton {   
    private static
Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }

優點: 寫法比較簡單,在類裝載的時候就完成例項化,避免了執行緒同步問題。

缺點: 在類裝載的時候就完成例項化,沒有達到 Lazy Loading 的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。

2、懶漢式 - 執行緒不安全 [不可用]

這種寫法起到了Lazy Loading的效果,但是隻能在單執行緒下使用。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

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

3、懶漢式 - 執行緒安全 同步方法 [不推薦]

雖然解決了執行緒不安全問題,但是效率很低,每個執行緒在想獲得類的例項時候,執行getInstance()方法都要進行同步。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

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

4、懶漢式 - 執行緒安全 同步程式碼塊 [不可用]

由於上一種實現方式同步效率太低,所以摒棄同步方法,改為同步產生例項化的的程式碼塊,但是這種同步並不能起到執行緒同步的作用。假如一個執行緒進入了 if (singleton == null) 判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

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

5、雙重校驗鎖(DCL) - 執行緒安全 [可用]

單例在第一次呼叫 getInstance 方法時例項化,在類載入時不自行例項化,即需要的時候再載入例項,為了處理多個執行緒同時訪問的問題,加入雙重鎖機制。

class LazySingleton {   
    private volatile static LazySingleton instance = null;   
    private LazySingleton() { }   

    public static LazySingleton getInstance() {   
        //第一重判斷  
        if (instance == null) {  
            //鎖定程式碼塊  
            synchronized (LazySingleton.class) {  
                //第二重判斷  
                if (instance == null) {  
                    instance = new LazySingleton(); //建立單例例項  
                }  
            }  
        }  
        return instance;   
    }  
}

6、靜態內部類 [推薦]

餓漢式單例類不能實現延遲載入,不管將來用不用始終佔據記憶體;懶漢式單例類執行緒安全控制煩瑣,而且效能受影響。

IoDH(Initialization Demand Holder) 技術能夠克服單例的上述缺點,在單例類中增加一個靜態內部類,在該內部類中建立單例物件,再將該單例物件通過 getInstance 方法返回給外部使用。

避免了執行緒不安全,延遲載入,效率高。

class Singleton {  
    private Singleton() { }  

    private static class HolderClass {  
        private final static Singleton instance = new Singleton();  
    }  

    public static Singleton getInstance() {  
        return HolderClass.instance;  
    }  
}

7、列舉 [推薦]

這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化,防止反序列化重新建立新的物件。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() { }  
}

分析

1、優點

系統記憶體中該類只存在一個物件,節省了系統資源,對於一些需要頻繁建立銷燬的物件,使用單例模式可以提高系統性能。

2、缺點

當想例項化一個單例類的時候,必須要記住使用相應的獲取物件的方法,而不是使用new,可能會給其他開發人員造成困擾,特別是看不到原始碼的時候。

3、適用環境

  • 需要頻繁的進行建立和銷燬的物件
  • 建立物件時耗時過多或耗費資源過多,但又經常用到的物件
  • 工具類物件
  • 頻繁訪問資料庫或檔案的物件

4、經驗

一般情況下,不建議使用第 2、3、4 種懶漢方式,建議使用第 1 種餓漢方式。只有在要明確實現 Lazy Loading 效果時,才會使用第 6 種靜態內部類。如果涉及到反序列化建立物件時,可以嘗試使用第 7 種列舉方式。如果有其他特殊的需求,可以考慮使用第 5 種雙重校驗鎖。

5、應用

  • java.lang.Runtime#getRuntime()
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}
  • java.awt.Toolkit#getDefaultToolkit()

不需要事先建立好,只要在第一次真正用到的時候再建立就可以了,所以使用懶漢式[同步方法]。

  • java.awt.Desktop#getDesktop()

參考