轉載請註明出處:http://cantellow.iteye.com/blog/838473html
第一種(懶漢,線程不安全):java
這種寫法lazy loading很明顯,可是致命的是在多線程不能正常工做。安全
第二種(懶漢,線程安全):多線程
這種寫法可以在多線程中很好的工做,並且看起來它也具有很好的lazy loading,可是,遺憾的是,效率很低,99%狀況下不須要同步。this
第三種(餓漢):spa
這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然致使類裝載的緣由有不少種,在單例模式中大多數都是調用getInstance方法, 可是也不能肯定有其餘的方式(或者其餘的靜態方法)致使類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。線程
第四種(餓漢,變種):htm
表面上看起來差異挺大,其實更第三種方式差很少,都是在類初始化即實例化instance。對象
第五種(靜態內部類):blog
這種方式一樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不一樣的是(很細微的差異):第三種和第四種方式是隻要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不必定被初始化。由於SingletonHolder類沒有被主動使用,只有顯示經過調用getInstance方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。想象一下,若是實例化instance很消耗資源,我想讓他延遲加載,另一方面,我不但願在Singleton類加載時就實例化,由於我不能確保Singleton類還可能在其餘的地方被主動使用從而被加載,那麼這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。
第六種(枚舉):
這種方式是Effective Java做者Josh Bloch 提倡的方式,它不只能避免多線程同步問題,並且還能防止反序列化從新建立新的對象,可謂是很堅強的壁壘啊,不過,我的認爲因爲1.5中才加入enum特性,用這種方式寫難免讓人感受生疏,在實際工做中,我也不多看見有人這麼寫過。
第七種(雙重校驗鎖):
這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5以後,雙重檢查鎖定纔可以正常達到單例效果。
總結
有兩個問題須要注意:
1.若是單例由不一樣的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每一個servlet使用徹底不一樣的類裝載器,這樣的話若是有兩個servlet訪問一個單例類,它們就都會有各自的實例。
2.若是Singleton實現了java.io.Serializable接口,那麼這個類的實例就可能被序列化和復原。無論怎樣,若是你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的實例。
對第一個問題修復的辦法是:
對第二個問題修復的辦法是:
對我來講,我比較喜歡第三種和第五種方式,簡單易懂,並且在JVM層實現了線程安全(若是不是多個類加載器環境),通常的狀況下,我會使用第三種方式,只有在要明確實現lazy loading效果時纔會使用第五種方式,另外,若是涉及到反序列化建立對象時我會試着使用枚舉的方式來實現單例,不過,我一直會保證個人程序是線程安全的,並且我永遠不會使用第一種和第二種方式,若是有其餘特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。
========================================================================
superheizai同窗總結的很到位:
不過通常來講,第一種不算單例,第四種和第三種就是一種,若是算的話,第五種也能夠分開寫了。因此說,通常單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,枚舉和靜態內部類。
我很高興有這樣的讀者,一塊兒共勉。