單例模式是比較常見的一種設計模式,目的是保證一個類只能有一個實例,並且自行實例化並向整個系統提供這個實例,避免頻繁建立對象,節約內存。數據庫
單例模式的應用場景不少,設計模式
好比咱們電腦的操做系統的回收站就是一個很好的單例模式應用,電腦上的文件、視頻、音樂等被刪除後都會進入到回收站中;還有計算機中的打印機也是採用單例模式設計的,一個系統中能夠存在多個打印任務,可是隻能有一個正在工做的任務;Web頁面的計數器也是用單例模式實現的,能夠不用把每次刷新都記錄到數據庫中。安全
經過回味這些應用場景,咱們對單例模式的核心思想也就有了更清晰的認識,下面就開始用代碼來實現。bash
在寫單例模式的代碼以前,咱們先簡單瞭解一下兩個知識點,關於類的加載順序和static關鍵字。多線程
類加載(classLoader)機制通常聽從下面的加載順序函數
若是類尚未被加載:性能
同時,加載類的過程是線程私有的,別的線程沒法進入。優化
若是類已經被加載:ui
靜態代碼塊和靜態變量不在重複執行,再建立類對象時,只執行與實例相關的變量初始化和構造方法。spa
一個類中若是有成員變量或者方法被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應用的頁面計數器就能夠用單例模式實現計數值的保存。
單例模式通常沒有接口,擴展的話除了修改代碼基本上沒有其餘途徑。