背景:單例模式模式是在編程中常常使用,他能夠對須要使用的資金進行一次性初始化,防止屢次初始化屢次資源釋放帶來性能的開銷。編程
最近在讀《JAVA併發編程的藝術》發現有些知識點不錯,整理出來。緩存
單例模式經常使用模式是懶漢模式和餓漢模式多線程
懶漢模式:就是用到時候才new出來。併發
餓漢模式:類一開始就加載好,可直接使用。性能
單線程狀況下,咱們會經過如下實現才生成一個懶漢模式的單例模式類。可是多線程訪問時候,這種實現不知足要求。優化
public class Singleton { public static Singleton instance = null; public Singleton() { //todo } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
上面實現,若是在多線程狀況下,可能出現多個線程同時訪問instance == null 出現問題。接下來,介紹幾種能夠避免多線程競爭訪問致使的問題。spa
(一)同步處理,延遲初始化,任什麼時候刻只有一個線程搶佔到訪問資源。線程
public class Singleton { public static Singleton instance = null; public Singleton() { //todo } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
可是這個方法也是弊端,因爲採用同步處理,synchronized致使性能開銷。若是getInstance()被多個線程頻繁調用,會致使程序執行性能降低。反之,若是getInstance()不被多個線程頻繁調用,那麼這個延遲初始化將是不錯的選擇。code
(二)雙重檢查鎖,優化方案一出現多線程頻繁調用instance(instance!=null)時阻塞的問題。對象
public class Singleton { public static Singleton instance = null; public Singleton() { //todo } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
優勢:若是instance不爲null, 多線程訪問instance就不會出現阻塞,提升了性能,減小了開銷。
缺點:因爲JAVA內存模型,不一樣JIT編譯器之間出現重排序問題,可能會出現線程A執行instance時instance==null,因而進入instance = new Singleton(), 線程B發現instance != null,可是這個instance可能未初始化。接下來我來細化下JMM執行過程方便理解。
instance = new Singleton() 能夠分解成如下幾步:
1. memory = allocate(); 分配對象的內存空間
2. ctorInstance(memory); 初始化對象
3. instance = memory; 設置instance指向剛分配的內存地址
因爲在爲僞代碼中2和3之間可能會出現重排序. 引入《JAVA併發編程的藝術》的圖。懶得本身畫了。
鑑於這個問題,咱們能夠有以下更佳解決方式,把instance定義爲volatile。JDK5以後引入volatile關鍵字,解決各個編譯器處理器緩存和總線內存之間數據一致性問題。當把對象聲明爲volatile後,2和3重排序問題的問題,在多進程中被禁止,即不容許重排序。
public class Singleton { public static volatile Singleton instance = null; public Singleton() { //todo } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
(三)若是你使用Spring來管理bean, @Autowired註釋自己就是單例模式,一開始就生成好一個bean實例,即爲餓漢模式。
@Component public class Singleton { @Autowired public Singleton(....){ //TODO } }