在GoF的23種設計模式中,單例模式是比較簡單的一種。然而,有時候越是簡單的東西越容易出現問題。下面就單例設計模式詳細的探討一下。數據庫
所謂單例模式,簡單來講,就是在整個應用中保證只有一個類的實例存在。就像是Java Web中的application,也就是提供了一個全局變量,用處至關普遍,好比保存全局數據,實現全局性的操做等。設計模式
一、最簡單的實現性能優化
首先,能想到的最簡單的實現是,把類的構造函數寫成private的,從而保證別的類不能實例化此類。而後在類中返回一個靜態示例並返回給調用者。這樣,調用者就能夠經過這個引用使用這個實例了。多線程
public class Singleton{ private static final Singleton singleton = new Singleton(); public static Singleton getInstance(){ return singleton; } private Singleton(){ } }
如上例,外部使用者若是須要使用SingletonClass的實例,只能經過getInstance()方法,而且它的構造方法是private的,這樣就保證了只能有一個對象存在。併發
二、性能優化--lazy loadedapp
上面的代碼雖然簡單,可是有一個問題----不管這個類是否被使用,都會建立一個instance對象。若是這個建立很耗時,好比說連接10000次數據庫(誇張一點啦....),而且這個類還不必定會被使用,那麼這個建立過程就是無用的,怎麼辦呢?函數
爲了解決這個問題,咱們想到的新的解決方案:性能
public class SingletonClass { private static SingletonClass instance = null; public static SingletonClass getInstance() { if(instance == null) { instance = new SingletonClass(); } return instance; } private SingletonClass() { } }
代碼的變化有倆處----首先,把 instance 設置爲 null ,知道第一次使用的時候判是否爲 null 來建立對象。由於建立對象不在聲明處,因此那個 final 的修飾必須去掉。優化
咱們來想象一下這個過程。要使用 SingletonClass ,調用 getInstance()方法,第一次的時候發現instance時null,而後就建立一個對象,返回出去;第二次再使用的時候,由於這個instance事static的,共享一個對象變量的,因此instance的值已經不是null了,所以不會再建立對象,直接將其返回。spa
這個過程就稱爲lazy loaded ,也就是遲加載-----直到使用的時候才經行加載。
三、同步
上面的代碼很清楚,也很簡單。然而就像那句名言:「80%的錯誤是由20%的代碼優化引發的」。單線程下,這段代碼沒什麼問題,但是若是是多線程呢,麻煩就來了,咱們來分析一下:
線程A但願使用SingletonClass,調用getInstance()方法。由於是第一次調用,A就發現instance是null的,因而它開始建立實例,就在這個時候,CPU發生時間片切換,線程B開始執行,它要使用SingletonClass,調用getInstance()方法,一樣檢測到instance是null——注意,這是在A檢測完以後切換的,也就是說A並無來得及建立對象——所以B開始建立。B建立完成後,切換到A繼續執行,由於它已經檢測完了,因此A不會再檢測一遍,它會直接建立對象。這樣,線程A和B各自擁有一個SingletonClass的對象——單例失敗!
解決的辦法也很簡單,那就是加鎖:
public class SingletonClass{ private static SingletonClass instance = null; public synchronized static SingletonClass getInstance(){ if(instance == null){ instance = new SingletonClass(); } return instance; } private SingletonClass(){ } }
只要getInstance()加上同步鎖,,一個線程必須等待另一個線程建立完後才能使用這個方法,這就保證了單利的惟一性。
四、又是性能
上面的代碼又是很清楚也很簡單的,然而,每每簡單的東西不夠理想。這段代碼毫無疑問存在性能的問題----synchronized修飾的同步塊但是要比通常的代碼慢上幾倍的!若是存在不少次的getInstance()調用,那性能問題就不得不考慮了?!!!
讓咱們來分析一下,到底是整個方法都必須加鎖,仍是牢牢其中某一句加鎖就足夠了?咱們爲何要加鎖呢?分析一下lazy loaded的那種情形的緣由,緣由就是檢測null的操做和建立對象的操做分離了,致使出現只有加同步鎖才能單利的惟一性。
若是這倆個操做可以原子的進行,那麼單利就已經保證了。因而,咱們開始修改代碼:
public class SingletonClass{ private static SingletonClass instance = null; public static SingletonClass getInstance(){ synchronized(SingletonClass.class){ if(instance == null){ instance = new SingletonClass(); } } return instance; } private SingletonClass(){ } }
首先去掉 getInstance() 的操做,而後把同步鎖加載到if語句上。可是,這樣的修改起不到任何做用:由於每次調用getInstance()的時候必然要經行同步,性能的問題仍是存在。若是............咱們事先判斷一下是否是爲null在去同步呢?
public class SingletonClass{ private static SingletonClass instance = null; public static SingletonClass getInstance(){ if(instance == null){ synchronized(SingletonClass.class){ if(instance == null){ instance = new SingletonClass(); } } } return instance; } private SingletonClass(){ } }
還有問題嗎?首先判斷instance是否是爲null,若是爲null在去進行同步,若是不爲null,則直接返回instance對象。
這就是double---checked----locking 設計實現單利模式。到此爲止,一切都很完美。咱們用一種很聰明的方式實現了單例模式。
五、從源頭檢查
下面咱們開始說編譯原理。所謂編譯,就是把源代碼」翻譯「成目標代碼----大可能是是指機器代碼----的過程。針對Java,它的目標代碼不是本地機器代碼,而是虛擬機代碼。編譯原理裏面有一個很重要的內容是編譯器優化。所謂編譯器優化是指,在不改變原來語義的狀況下,經過調整語句順序,來讓程序運行的更快。這個過程成爲reorder。
要知道,JVM只是一個標準,並非實現。JVM中並無規定有關編譯器優化的內容,也就是說,JVM實現能夠自由的進行編譯器優化。
下面來想一下,建立一個變量須要哪些步驟呢?一個是申請一塊內存,調用構造方法進行初始化操做,另外一個是分配一個指針指向這塊內存。這兩個操做誰在前誰在後呢?JVM規範並無規定。那麼就存在這麼一種狀況,JVM是先開闢出一塊內存,而後把指針指向這塊內存,最後調用構造方法進行初始化。
下面咱們來考慮這麼一種狀況:線程A開始建立SingletonClass的實例,此時線程B調用了getInstance()方法,首先判斷instance是否爲null。按照咱們上面所說的內存模型,A已經把instance指向了那塊內存,只是尚未調用構造方法,所以B檢測到instance不爲null,因而直接把instance返回了——問題出現了,儘管instance不爲null,但它並無構造完成,就像一套房子已經給了你鑰匙,但你並不能住進去,由於裏面尚未收拾。此時,若是B在A將instance構造完成以前就是用了這個實例,程序就會出現錯誤了!
因而,咱們想到了下面的代碼:
public class SingletonClass { private static SingletonClass instance = null; public static SingletonClass getInstance() { if (instance == null) { SingletonClass sc; synchronized (SingletonClass.class) { sc = instance; if (sc == null) { synchronized (SingletonClass.class) { if(sc == null) { sc = new SingletonClass(); } } instance = sc; } } } return instance; } private SingletonClass() { } }
public class SingletonClass { private volatile static SingletonClass instance = null; public static SingletonClass getInstance() { if (instance == null) { synchronized (SingletonClass.class) { if(instance == null) { instance = new SingletonClass(); } } } return instance; } private SingletonClass() { } }
然而,這只是JDK1.5以後的Java的解決方案,那以前版本呢?其實,還有另外的一種解決方案,並不會受到Java版本的影響:
public class SingletonClass { private static class SingletonClassInstance { private static final SingletonClass instance = new SingletonClass(); } public static SingletonClass getInstance() { return SingletonClassInstance.instance; } private SingletonClass() { } }