定義html
單例模式最初的定義出現於《設計模式》(艾迪生維斯理, 1994):「保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。」java
Java中單例模式定義:「一個類有且僅有一個實例,而且自行實例化向整個系統提供。」spring
主要解決:一個全局使用的類頻繁地建立與銷燬。設計模式
什麼時候使用:當您想控制實例數目,節省系統資源的時候。安全
特色框架
A.這些類只能有一個實例;jvm
B.可以自動實例化;函數
C.這個類對整個系統可見,即必須向整個系統提供這個實例。優化
優勢.net
實例
實現方式
單例模式的實現一般有兩種方式:「餓漢式」和「懶漢式」。
單例模式是將將構造方法限定爲private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的惟一實例只能經過提供的入口得到[例如getInstance()方法], 事實上,經過Java反射機制是可以實例化構造方法爲private的類的,那基本上會使全部的Java單例實現失效,咱們不考慮java反射機制
懶漢式
懶漢式在類建立的時候不會去建立實例,在第一次調用時纔會去建立,可是會有線程安全問題
餓漢式
在類建立的時候建立出實例,這樣只會有一個實例被建立,沒有線程安全不安全的問題
單例模式初版
public class Singleton { private Singleton() {} //私有構造函數 private static Singleton instance = null; //單例對象 // 靜態工廠方法 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
爲何這樣寫呢?咱們來解釋幾個關鍵點:
1.要想讓一個類只能構建一個對象,天然不能讓它隨便去作new操做,所以Signleton的構造方法是私有的。
2.instance是Singleton類的靜態成員,也是咱們的單例對象。它的初始值能夠寫成Null,也能夠寫成new Singleton()。至於其中的區別後來會作解釋。
3.getInstance是獲取單例對象的方法。
若是單例初始值是null,還未構建,則構建單例對象並返回。這個寫法屬於單例模式當中的懶漢模式。
若是單例對象一開始就被new Singleton()主動構建,則再也不須要判空操做,這種寫法屬於餓漢模式。
這兩個名字很形象:餓漢主動找食物吃,懶漢躺在地上等着人喂。
爲何說剛纔的代碼不是線程安全呢?
假設Singleton類剛剛被初始化,instance對象仍是空,這時候兩個線程同時訪問getInstance方法:
由於Instance是空,因此兩個線程同時經過了條件判斷,開始執行new操做:
這樣一來,顯然instance被構建了兩次。讓咱們對代碼作一下修改:
單例模式第二版:
public class Singleton { private Singleton() {} //私有構造函數 private static Singleton instance = null;//單例對象 //靜態工廠方法 public static Singleton getInstance() { if (instance == null) { //雙重檢測機制 synchronized (Singleton.class) {//同步鎖 if (instance == null) { //雙重檢測機制 instance = new Singleton(); } } } return instance; } }
爲何這樣寫呢?咱們來解釋幾個關鍵點:
1.爲了防止new Singleton被執行屢次,所以在new操做以前加上Synchronized 同步鎖,鎖住整個類(注意,這裏不能使用對象鎖)。
2.進入Synchronized 臨界區之後,還要再作一次判空。由於當兩個線程同時訪問的時候,線程A構建完對象,線程B也已經經過了最初的判空驗證,不作第二次判空的話,線程B仍是會再次構建instance對象。
3.若是已經存在該對象的實例,再次進行訪問時不用再對對象進行加鎖操做。
像這樣兩次判空的機制叫作雙重檢測機制。
假設這樣的場景,當兩個線程一先一後訪問getInstance方法的時候,當A線程正在構建對象,B線程剛剛進入方法:
這種狀況表面看似沒什麼問題,要麼Instance還沒被線程A構建,線程B執行 if(instance == null)的時候獲得false;要麼Instance已經被線程A構建完成,線程B執行 if(instance == null)的時候獲得true。
真的如此嗎?答案是否認的。這裏涉及到了JVM編譯器的指令重排。
指令重排是什麼意思呢?好比java中簡單的一句 instance = new Singleton,會被編譯器編譯成以下JVM指令:
memory =allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設置instance指向剛分配的內存地址
可是這些指令順序並不是一成不變,有可能會通過JVM和CPU的優化,指令重排成下面的順序:
memory =allocate(); //1:分配對象的內存空間
instance =memory; //3:設置instance指向剛分配的內存地址
ctorInstance(memory); //2:初始化對象
當線程A執行完1,3,時,instance對象還未完成初始化,但已經再也不指向null。此時若是線程B搶佔到CPU資源,執行 if(instance == null)的結果會是false,從而返回一個沒有初始化完成的instance對象。以下圖所示:
如何避免這一狀況呢?咱們須要在instance對象前面增長一個修飾符volatile。
單例模式第三版:
public class Singleton { private Singleton() { } //私有構造函數 private volatile static Singleton instance = null; //單例對象 //靜態工廠方法 public static Singleton getInstance() { if (instance == null) { //雙重檢測機制 synchronized (Singleton.class) { //同步鎖 if (instance == null) { //雙重檢測機制 instance = new Singleton(); } } } return instance; } }
The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.
通過volatile的修飾,當線程A執行instance = new Singleton的時候,JVM執行順序是什麼樣?始終保證是下面的順序:
memory =allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設置instance指向剛分配的內存地址
如此在線程B看來,instance對象的引用要麼指向null,要麼指向一個初始化完畢的Instance,而不會出現某個中間態,保證了安全。
1. volatile關鍵字不但能夠防止指令重排,也能夠保證線程訪問的變量值是主內存中的最新值。有關volatile的詳細原理,自行百科。
用靜態內部類實現單例模式:
public class Singleton{ private static class LazyHolder{ private static final Singleton INSTANCE= newSingleton(); } private Singleton(){} public static Singleton getInstance(){ returnLazyHolder.INSTANCE; } }
這裏有幾個須要注意的點:
1.從外部沒法訪問靜態內部類LazyHolder,只有當調用Singleton.getInstance方法的時候,才能獲得單例對象INSTANCE。
2.INSTANCE對象初始化的時機並非在單例類Singleton被加載的時候,而是在調用getInstance方法,使得靜態內部類LazyHolder被加載的時候。所以這種實現方式是利用classloader的加載機制來實現懶加載,並保證構建單例的線程安全
總結:
單例模式是爲了實現整個系統內部在運行過程當中只有一個實例,(餓漢 懶漢 線程安全 雙重判空 volidate防止jvm指令重排)
轉自: http://www.sohu.com/a/206960903_479559
單例模式其餘的實現方式,能夠參考:http://www.runoob.com/design-pattern/singleton-pattern.html
也能夠經過http://blog.csdn.net/jason0539/article/details/23297037/等博文,加深本身對單例模式的理解