單例模式是一個比較"簡單"的模式,其定義以下:java
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。git
或者數據庫
Ensure a class has only one instance, and provide a global point of access to it.設計模式
確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。安全
請注意"簡單"二字的雙引號,說它簡單它也簡單,可是要想用好、用對其實並不那麼簡單,爲何這麼說?markdown
延遲加載多線程
線程安全jvm
破壞單例的狀況ide
序列化函數
若是Singleton類是可序列化的,僅僅在生聲明中加上implements Serializable是不夠的。爲了維護並保證Singleton,必須聲明全部實例域都是瞬時(transient)的,而且提供一個readResolve方法。不然,每次反序列化一個序列化的實例時,都會建立一個新的對象。
反射
受權的客戶端能夠經過反射來調用私有構造方法,藉助於AccessibleObject.setAccessible方法便可作到 。若是須要防範這種攻擊,請修改構造函數,使其在被要求建立第二個實例時拋出異常。
private Singleton() { System.err.println("Singleton Constructor is invoked!"); if (singleton != null) { System.err.println("實例已存在,沒法初始化!"); throw new UnsupportedOperationException("實例已存在,沒法初始化!"); } } } 複製代碼
對象複製
在Java中,對象默認是不能夠被複制的,若實現了Cloneable接口,並實現了clone方法,則能夠直接經過對象複製方式建立一個新對象,對象複製是不用調用類的構造函數,所以即便是私有的構造函數,對象仍然能夠被複制。在通常狀況下,類複製的狀況不須要考慮,不多會出現一個單例類會主動要求被複制的狀況,解決該問題的最好方法就是單例類不要實現Cloneable接口。
類加載器
若是單例由不一樣的類裝載器裝入,那便有可能存在多個單例類的實例。
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 複製代碼
爲解決懶漢式"線程安全問題",能夠將getInstance()設置爲同步方法,因而就有了第二種實現方式:
public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 複製代碼
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } } 複製代碼
若是不是特別須要延遲加載的場景,能夠優先考慮餓漢式
public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 複製代碼
優勢:延遲加載,線程安全,而且效率也很不錯
缺點:實現相對複雜一點,JDK1.5之後才支持volatile
說明
這裏針對volatile多說兩句,不少書上和網上的雙重檢查鎖實例都沒有加volatile,事實上這是不正確的
首先,volatile的兩層含義:
這裏咱們用到的主要是第二個語義。那麼什麼是指令重排序呢,就是指編譯器和處理器爲了優化程序性能而對指令序列進行排序的一種手段。簡單理解,就是編譯器對咱們的代碼進行了優化,在實際執行指令的的時候可能與咱們編寫的順序不一樣,只保證程序執行結果與源代碼相同,卻不保證明際指令的順序與源代碼相同。
singleton = new Singleton();
這段代碼在jvm執行時實際分爲三步:
因爲"指令重排"的優化,極可能執行步驟爲1-3-2,即:對象並無實例化完成但引用已是非空了,也就是在第二處判空的地方爲false,直接返回singleton——一個未完成實例化的對象引用。
這裏涉及到Java內存模型、內存屏障等知識點,本文主要介紹單例模式,所以再也不贅述,有興趣的同窗能夠自行百度
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 複製代碼
與餓漢式的區別是,靜態內部類SingletonHolder只有在getInstance()方法第一次調用的時候纔會被加載(實現了延遲加載效果)。
所以靜態內部類實現方式既能保證線程安全,也能保證單例的惟一性,同時也具備延遲加載特性
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } } 複製代碼
優勢:枚舉方式具備以上全部實現方式的優勢,同時還無償地提供了序列化機制,防止屢次實例化
缺點:JDK1.5之後才支持enum;普及度較前幾種方式不高
在一個系統中,要求一個類有且僅有一個對象,若是出現多個對象就會出現「不良反應」,能夠採用單例模式,具體的場景以下:
參考文獻:《設計模式之禪》、《Effective Java》