保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。html
簡單來講,也就是須要保證一個類在整個應用程序的生命週期內,只能存在一個實例(沒有也行)。爲了達成這個目標,應該要作到如下幾點:java
首先,咱們按照前面說的三點,寫了如下代碼:設計模式
public class Singleton { private Singleton instance; private Singleton(){} public Singleton GetInstance(){ return instance; } }
這樣,顯然是有問題的。安全
一是獲取實例的公開方法getInstance,只是生命成public的,外部在沒有Singleton的實例的狀況下仍是不能調用,就成了一個悖論了。因此須要把GetInstance方法聲明爲靜態的。一樣,因爲須要被靜態方法調用,同時還要用來保持惟一的實例,instance也須要聲明爲靜態的。多線程
二就是還缺乏了對instance變量的初始化,即對構造器的調用。咱們既能夠再聲明instance是就進行初始化,也能夠在靜態代碼段中對它進行初始化。併發
因而乎,就有了下面兩個版本的實現:oracle
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton GetInstance(){ return instance; } }
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton(){} public static Singleton GetInstance(){ return instance; } }
因爲這兩種方法沒有實質上的區別,都是在類被加載的時候就進行了實例的初始化(因此被稱爲餓漢式)。這也是餓漢式的下的兩個特色:性能
因爲餓漢式在類加載時就完成了實例化,致使了可能存在性能浪費,因此咱們就考慮看看能不能在類被使用時才被實例化呢。若是據說過『懶加載』這個的詞話,應該就會以爲這很easy了,在以前的基礎上,很輕鬆就寫出了下面代碼(懶漢式的單例)。優化
public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static LazySingleton GetInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
乍一看,已是能夠了,至少在單線程下已經沒問題了。可是在多線程的場景下呢,很容易就會產生A、B兩個線程同時調用GetInstance方法,A線程先判斷instance爲null,準備進行實例化,但在實例化以前B線程也進行了instance爲null的判斷,最終結果是兩個線程分別調用了一次私有構造器進行實例化,第二次實例化的結果會將第一次的覆蓋掉。線程
因此懶漢式有如下特色:
爲了解決上面方法的缺陷,也就是所謂的線程不安全,咱們找到了synchronized這個關鍵字,也只加了這個關鍵字,獲得下面的代碼。
public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static synchronized LazySingleton GetInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
與前一種方法相比,在實現上只是在getInstance方法上增長了synchronized關鍵字,使得GetInstance方法同步,同一時間只能有一個線程執行這個方法,那咱們以前說的線程不安全的問題顯然就不在了,這樣是否是就完美來呢?世界沒那麼美好,咱們又引入了新的問題。
因爲是在整個GetInstance方法上加鎖(同步),可是由於實際上只須要進行一次實例化(也只容許進行一次),因此絕大多數場景下是不須要同步的,因此在併發場景下會致使效率下降,至關於多車道在這裏併成單車道了。
既然還有問題,那咱們就來繼續進行優化。上面的方法由於把整個GetInstance方法設置爲synchronized,因此致使多線程在這裏受阻,那咱們把同步的範圍縮小一點兒,看看狀況會不會好一些。
public class LazySingleton { private static volatile LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if (instance == null) { synchronized(LazySingleton.class){ if (instance == null) { instance = new LazySingleton(); } } } return instance; } }
優化以後,把鎖定範圍進行了收縮,只在須要進行初始化實例時才進行同步,以後就再也不進行同步。
這樣咱們就獲得了一種效率較高,而且線程安全的單例模式的構造方法。
PS. 若是有仔細看代碼,您或許會發現咱們在聲明instance變量的時候,用了一個volatile關鍵字,若是須要一些解釋的話,能夠參考Java中的volatile在使用雙層檢查實現單例模式的解讀,後面我也可能來單獨說一下這個。
還有一種使用靜態內部類來實現的單例,也被各類推薦。由於內部靜態類是要在有引用了之後纔會裝載到內存的,這樣就一樣實現了懶加載;同時,靜態內部類的靜態變量的初始化,也是在被加載時進行的初始化,自然的完成來對進程安全的控制。
public class InnerStaticClassSingleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static InnerStaticClassSingleton getInstance() { return SingletonInstance.INSTANCE; } }
To Be Continued