單線程下的Singleton的穩定性是極好的,可分爲兩大類:
1.Eager(餓漢型): 類加載時當即建立對象。java
public class EagerSingleton { //1. 類加載時就當即產生實例對象,經過設置靜態變量被外界獲取 //2. 並使用private保證封裝安全性 private static EagerSingleton eagerSingleton = new EagerSingleton(); //3. 經過構造方法的私有化,不容許外部直接建立對象,確保單例的安全性 private EagerSingleton(){ } public static EagerSingleton getEagerSingleton(){ return eagerSingleton; }
2.Lazy(懶漢型):類加載時沒有當即建立對象,等到第一個用戶獲取才進行實例化。git
public class LazySingleton { //1. 類加載時並無建立惟一實例 private static LazySingleton lazySingleton; private LazySingleton() { } //二、提供一個獲取實例的靜態方法 public static LazySingleton getLazySingleton() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; }
就性能方面而言,LazySingleton 明顯優於 EagerSingleton ,若類的加載須要耗費大量的資源(e.g. 讀取大文件信息),那麼LazySingleton 的優點顯而易見。但經過閱讀代碼,很容易發現一個致命問題。多線程間如何保持安全性?github
下面將對多線程併發問題進行解析:編程
解決該問題的關鍵在於兩方面:1.同步; 2.性能;
有線程A,線程B同時調用getLazySingleton()獲取實例,A調用時判斷instance爲null,正準備進行初始化時,忽然A線程被掛起了,此時對象並未實例化成功,更糟的事隨後發生,B線程被運行了,他也判斷了instance爲null,此時A,B都進入了實例化階段,這樣就產生了兩個實例,破壞單例原則。
如何解救呢?
做爲一個java的開發者,對synchronized必定不陌生,提到多線程,大部分人想到的都是他(JDK6後,他的性能提高巨大,解決簡單併發,很是適用)。緩存
那就讓咱們用synchronized來嘗試解決吧:
//由synchronized進行同步加鎖 public synchronized static LazySingleton getLazySingleton() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; }
如此同步問題看似解決,可是做爲一個開發者,最重要的是性能的保障,使用synchronized有利有弊,因爲加鎖操做,代碼段被加上悲觀鎖,只有等一個請求完成,下個請求才能進入執行。一般加上synchronized關鍵字的代碼片會比同等量級的代碼慢上幾倍,這是咱們不肯見到的。那如何避免這一問題呢?在java對synchronized的定義裏有這樣的建議:越遲使用synchronized,性能越優(細化鎖)。安全
###### 2.所以,咱們須要開始解決性能的問題了。按照synchronized優化: ######多線程
public class DoubleCheckLockSingleton { //使用volatile保證每次取值不是從緩存中取,而是從真正對應的內存地址中取.(下文解釋) private static volatile DoubleCheckLockSingleton doubleCheckLockSingleton; private DoubleCheckLockSingleton(){ } public static DoubleCheckLockSingleton getDoubleCheckLockSingleton(){ //配置雙重檢查鎖(下文解釋) if(doubleCheckLockSingleton == null){ synchronized (DoubleCheckLockSingleton.class) { if(doubleCheckLockSingleton == null){ doubleCheckLockSingleton = new DoubleCheckLockSingleton(); } } } return doubleCheckLockSingleton; } }
上述源碼就是經典的volatile關鍵字(JDK1.5 後重生)+雙重檢查鎖(DoubleCheck),最大程度的優化了sychronized帶來的性能開銷。下面將爲你們解釋volatile與DoubleCheck。併發
1.volatileapp
是在JDK1.5後才正式被實現使用的,以前的版本只是定義了該關鍵字,未有具體實現。若想理解volatile就必須對JVM自身的內存管理有些許瞭解:
1.1 遵循着摩爾定律,內存的讀寫速度已遠不能知足CPU,所以現代計算機引入了在CPU上添加高速緩存的機制,由緩存預讀取內存的值,並暫存於緩存中,經過計算,再更新內存中的相應值。性能
**1.2** 而JVM模仿PC的這一作法,在內存中劃分了本身的**工做內存**,該部份內存做用與高速緩存一致,很顯著的提升JVM工做效率,但凡事都有利有弊,這一作法也致使工做內存與其餘內存通訊時容易致使傳輸上的問題。volatile的一個功能就是強制的從內存中讀取最新的值,避免緩存與內存不一致的情況。
1.3 volatile的另外一個功能也是和JVM相關,即JVM會經過自身的判斷,將源碼的執行順序重排,保證指令流水線連貫性,以達到最優的執行方案。這種作法提升了性能,但對DoubleCheck卻會產生意想外的結果,兩線程可能互相干擾。而volatile提供了happens-before guarantee(寫優先於讀),使對象不被幹擾,保證安全的穩定性。
2.DoubleCheck
這是現代編程的遺留,假設進入同步塊以後,對象已被實例化,此時需再次進行判斷。
固然還有一種官方推薦的單例實現方法:
因爲類的構造在定義中已經是原子性的,所以上述的各類問題都不會再產生,是一種很好的單例實現方式,推薦使用。
//使用內部類進行單例構造 public class NestedClassSingleton { private NestedClassSingleton(){ } private static class SingletonHolder{ private static final NestedClassSingleton nestedClassSingleton = new NestedClassSingleton(); } public static NestedClassSingleton getNestedClassSingleton(){ return SingletonHolder.nestedClassSingleton; } }
祝近安
ooooor