設計模式:單例模式 (關於餓漢式和懶漢式)

定義

單例模式是比較常見的一種設計模式,目的是保證一個類只能有一個實例,並且自行實例化並向整個系統提供這個實例,避免頻繁建立對象,節約內存。數據庫

單例模式的應用場景不少,設計模式

好比咱們電腦的操做系統的回收站就是一個很好的單例模式應用,電腦上的文件、視頻、音樂等被刪除後都會進入到回收站中;還有計算機中的打印機也是採用單例模式設計的,一個系統中能夠存在多個打印任務,可是隻能有一個正在工做的任務;Web頁面的計數器也是用單例模式實現的,能夠不用把每次刷新都記錄到數據庫中。安全

經過回味這些應用場景,咱們對單例模式的核心思想也就有了更清晰的認識,下面就開始用代碼來實現。bash

在寫單例模式的代碼以前,咱們先簡單瞭解一下兩個知識點,關於類的加載順序和static關鍵字。多線程

類加載順序

類加載(classLoader)機制通常聽從下面的加載順序函數

若是類尚未被加載:性能

  • 先執行父類的靜態代碼塊和靜態變量初始化,靜態代碼塊和靜態變量的執行順序跟代碼中出現的順序有關。
  • 執行子類的靜態代碼塊和靜態變量初始化。
  • 執行父類的實例變量初始化
  • 執行父類的構造函數
  • 執行子類的實例變量初始化
  • 執行子類的構造函數

同時,加載類的過程是線程私有的,別的線程沒法進入。優化

若是類已經被加載:ui

靜態代碼塊和靜態變量不在重複執行,再建立類對象時,只執行與實例相關的變量初始化和構造方法。spa

static關鍵字

一個類中若是有成員變量或者方法被static關鍵字修飾,那麼該成員變量或方法將獨立於該類的任何對象。它不依賴類特定的實例,被類的全部實例共享,只要這個類被加載,該成員變量或方法就能夠經過類名去進行訪問,它的做用用一句話來描述就是,不用建立對象就能夠調用方法或者變量,這簡直就是爲單例模式的代碼實現量身打造的。

下面將列舉幾種單例模式的實現方式,其關鍵方法都是用static修飾的,而且,爲了不單例的類被頻繁建立對象,咱們能夠用private的構造函數來確保單例類沒法被外部實例化。

懶漢和餓漢

在程序編寫上,通常將單例模式分爲兩種,分別是餓漢式和懶漢式,

餓漢式:在類加載時就完成了初始化,因此類加載比較慢,但獲取對象的速度快。

懶漢式:在類加載時不初始化,等到第一次被使用時才初始化。

代碼實現

一、餓漢式 (可用)

public class Singleton {

    private final static Singleton INSTANCE = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return INSTANCE;
    }

}
複製代碼

這是比較常見的寫法,在類加載的時候就完成了實例化,避免了多線程的同步問題。固然缺點也是有的,由於類加載時就實例化了,沒有達到Lazy Loading (懶加載) 的效果,若是該實例沒被使用,內存就浪費了。

二、普通的懶漢式 (線程不安全,不可用)

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

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

}
複製代碼

這是懶漢式中最簡單的一種寫法,只有在方法第一次被訪問時纔會實例化,達到了懶加載的效果。可是這種寫法有個致命的問題,就是多線程的安全問題。假設對象還沒被實例化,而後有兩個線程同時訪問,那麼就可能出現屢次實例化的結果,因此這種寫法不可採用。

三、同步方法的懶漢式 (可用)

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

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

}
複製代碼

這種寫法是對getInstance()加了鎖的處理,保證了同一時刻只能有一個線程訪問並得到實例,可是缺點也很明顯,由於synchronized是修飾整個方法,每一個線程訪問都要進行同步,而其實這個方法只執行一次實例化代碼就夠了,每次都同步方法顯然效率低下,爲了改進這種寫法,就有了下面的雙重檢查懶漢式。

四、雙重檢查懶漢式 (可用,推薦)

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;
    }

}
複製代碼

這種寫法用了兩個if判斷,也就是Double-Check,而且同步的不是方法,而是代碼塊,效率較高,是對第三種寫法的改進。爲何要作兩次判斷呢?這是爲了線程安全考慮,仍是那個場景,對象還沒實例化,兩個線程A和B同時訪問靜態方法並同時運行到第一個if判斷語句,這時線程A先進入同步代碼塊中實例化對象,結束以後線程B也進入同步代碼塊,若是沒有第二個if判斷語句,那麼線程B也一樣會執行實例化對象的操做了。

五、靜態內部類 (可用,推薦)

public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

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

}
複製代碼

這是不少開發者推薦的一種寫法,這種靜態內部類方式在Singleton類被裝載時並不會當即實例化,而是在須要實例化時,調用getInstance方法,纔會裝載SingletonInstance類,從而完成對象的實例化。

同時,由於類的靜態屬性只會在第一次加載類的時候初始化,也就保證了SingletonInstance中的對象只會被實例化一次,而且這個過程也是線程安全的。

六、枚舉 (可用、推薦)

public enum Singleton {
    INSTANCE;
}
複製代碼

這種寫法在《Effective JAVA》中大爲推崇,它能夠解決兩個問題:

1)線程安全問題。由於Java虛擬機在加載枚舉類的時候會使用ClassLoader的方法,這個方法使用了同步代碼塊來保證線程安全。

2)避免反序列化破壞對象,由於枚舉的反序列化並不經過反射實現。

好了,單例模式的幾種寫法就介紹到這了,最後簡單總結一下單例模式的優缺點

單例模式的優缺點

優勢

單例類只有一個實例,節省了內存資源,對於一些須要頻繁建立銷燬的對象,使用單例模式能夠提升系統性能;

單例模式能夠在系統設置全局的訪問點,優化和共享數據,例如前面說的Web應用的頁面計數器就能夠用單例模式實現計數值的保存。

缺點

單例模式通常沒有接口,擴展的話除了修改代碼基本上沒有其餘途徑。

相關文章
相關標籤/搜索