單例,故名思議,一個只能建立一個實例的類。java
單例被普遍應用於Spring的bean(默認)、線程池、數據庫鏈接池、緩存,還有其餘一些無狀態的類如servlet。程序員
一個不必多例的類實現了單例能夠節約空間(顯而易見),節省資源(線程、數據庫鏈接)。數據庫
單例模式有這麼多好處,那咱們來實現它吧,首先想到的是建立一個對象要使用new方法,new方法調用的是類的構造函數,想要不被程序員隨意的new對象能夠將類的構造函數設爲私有,而後再提供一個獲取這個類實例的方法,因此就有了下面這個實現。緩存
一、只能正確運行在單線程模式下的單例實現:安全
1 public class SingletonSingleThread { 2 3 private static SingletonSingleThread instance; 4 5 private SingletonSingleThread(){} 6 7 public static SingletonSingleThread getInstance(){ 8 if(instance == null){ 9 instance = new SingletonSingleThread(); 10 } 11 return instance; 12 } 13 }
以上代碼只能保證在單線程下運行,在多線程環境下可能有多個線程在第8行時判斷到instance當前是指向null,而後都去執行第9行的代碼。多線程
若是讀者瞭解過volatile修飾變量的做用,可能想到將以上代碼修改爲以下形式,由於volatile能夠保證線程之間變量的「可見性」,就能夠保證每一個線程在第8行判斷的時候都是instance的最新引用了?函數
1 public class SingletonSingleThread { 2 3 private static volatile SingletonSingleThread instance; 4 5 private SingletonSingleThread(){} 6 7 public static SingletonSingleThread getInstance(){ 8 if(instance == null){ 9 instance = new SingletonSingleThread(); 10 } 11 return instance; 12 } 13 }
可是第8行和第9行是兩行代碼,並非原子操做,徹底可能出現線程A執行經過第8行校驗,準備執行第9行的時候,另外一個線程B來到第8行校驗,也經過了校驗。性能
實際上就算是代碼中的一行指令也不是原子操做,在編譯成.class文件後未必是一行字節碼,就算是一行字節碼,在解釋執行或即時編譯執行轉化成機器碼時也可能對應多條指令,以上結論原理不在本文介紹範圍以內。spa
思考:java裏有一個很方便的實現線程安全的synchronized修飾符,加上不就實現線程安全了嗎?是的,下面這個實現就是在getInstance方法上簡單加上synchronized修飾符。線程
二、對性能不敏感的多線程安全單例實現:
1 public class SingletonSlow { 2 3 private static SingletonSlow instance; 4 5 private SingletonSlow(){} 6 7 public synchronized static SingletonSlow getInstance(){ 8 if(instance == null){ 9 instance = new SingletonSlow(); 10 } 11 return instance; 12 } 13 }
若是程序對一個單例實現的getInstance()方法效率不敏感可使用這種實現方式。好處就是直觀,簡單。壞處也顯而易見,只有當SingletonSlow對象第一次被建立時是須要同步的,以後的調用synchronized都將是額外的負擔。
思考:能不能只在第一次調用對象實例須要建立的時候才同步代碼,其餘時候不一樣步代碼的方法呢?有的。
三、DCL單例模式(雙重鎖檢查) 注意:這種方式只能應用於JDK1.5及之後的版本,JDK1.5解決了volatile沒法實現雙重鎖單例的bug:
1 public class SingletonDCL { 2 3 private volatile static SingletonDCL instance; 4 5 private SingletonDCL(){} 6 7 public static SingletonDCL getInstance(){ 8 if(instance == null){ 9 synchronized(SingletonDCL.class){ 10 if(instance == null){ 11 instance = new SingletonDCL(); 12 } 13 } 14 } 15 return instance; 16 } 17 }
DCL方式雖然也用到了同步保證單例,可是它的效率要遠遠高於第2種實現方式。首先實例變量instance用volatile修飾,保證第8行拿到的是instance的最新引用,這行判斷能夠快速逃避instance已經被初始化的狀況,固然這行代碼仍是會出現多個線程都判斷爲真的狀況,第9行的代碼保證了10-12行代碼只有一個線程能執行,第10行從新判斷instance引用爲空的意義在於解決如下狀況的發生:
①若線程A和B同時經過第8行代碼判斷,等待進入同步塊依次執行,若A先執行,則B得到執行時間時instance已經被線程A初始化了。
②若線程A經過第8行代碼判斷,進入同步塊開始執行11行代碼未執行完時,線程B執行到第8行判斷經過,這時A同步代碼塊執行完畢,B雖然接下來不須要等待直接進入同步塊,但instance也是已經被初始化過的。
思考:這個寫法看上去很難理解,我想到一個實現方式,既然是靜態變量類型,直接在變量聲明時賦值,或者寫一個靜態塊初始化不就行了,靜態類型的變量初始化是伴隨着類加載進行的,不也是線程安全的嗎?確實。
四、利用類加載器實現的急切加載單例:
1 public class SingletonEagerly { 2 3 private static SingletonEagerly instance = new SingletonEagerly(); 4 5 private SingletonEagerly(){} 6 7 public static SingletonEagerly getInstance(){ 8 return instance; 9 } 10 }
爲何叫急切加載單例呢,由於依賴於java的類加載機制,類被加載的時機未必是在須要使用類的對象時,也許在還不須要這個類的實例的時候類的實例就已經被初始化了,若是這個單例很耗費資源,咱們確定想採用懶加載的方式去實現他。
這種實現還有一個缺陷就是依賴於當前線程的ClassLoader(),若是類被多個ClassLoader加載(好比servlet),那就沒法保證單例了。
思考:怎樣避免類的實例只有在真正須要它的時候才被初始化呢? 能夠用私有靜態內部類實現。
五、利用類加載器實現的懶加載單例:
1 public class SingletonLazyByClassLoader { 2 3 private SingletonLazyByClassLoader(){} 4 5 public static SingletonLazyByClassLoader getInstance(){ 6 return SingletonHolder.instance; 7 } 8 9 private static class SingletonHolder{ 10 private static final SingletonLazyByClassLoader instance= new SingletonLazyByClassLoader(); 11 } 12 }
將類的惟一實例放在內部靜態類裏,這樣外部只要不調用getInstance方法就不會有類加載器加載去加載內部靜態類,而內部靜態類是私有的,因此只有第一次instance方法被調用的時候纔會初始化了類的實例變量。
遺憾的是這種實現方式仍然沒法避免ClassLoader不同的問題。
若是實在對getInstance效率無要求,使用方式2實現單例最簡單直觀;
若是程序運行在Jdk1.5及以上的環境,又不會以爲實現麻煩,強烈推薦使用方式3實現單例,高效、可靠;
若是嫌方式3麻煩或者運行在Jdk1.5之前的版本,則根據是否對懶加載有需求使用方式5或方式4,同時要保證程序中用到的類加載器是AppClassLoader或先代加載器是AppClassLoader(而且遵循雙親委託機制),不然單例會失效。