單例模式的五種建立模式
阿新 • • 發佈:2019-01-06
一般單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,列舉和靜態內部類。
package com.spring.utils; /** * 單列模式的七種實現方式 */ public class Singleton { /** * 第一種(懶漢,執行緒不安全) */ /*private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; }*/ /** * 第二種(懶漢,執行緒安全) * 這種寫法能夠在多執行緒中很好的工作,而且看起來它也具備很好的lazy loading, 但是,遺憾的是,效率很低,99%情況下不需要同步。 */ /*private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }*/ /** * 第三種(餓漢) * 這種方式基於classloder機制,在深度分析java的ClassLoader機制(原始碼級別) 和 java類的載入,連結和初始化 * 兩個文章中有關於Classload機制的執行緒安全問題的介紹,避免了多執行緒的同步問題,不過,instance在類裝載時就例項化, * 雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫getInstance方法,但是不能確定有其他的方式(或者其他的靜態方法) * 導致類裝載,這時候初始化instance顯然沒有達到lazy loading 的效果。 */ /* private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; }*/ /** * 第四種(餓漢,變種) * 表面看起來差別挺大,其實跟第三種方式差不多,都在類載入的時候初始化例項instance */ /*private Singleton instance = null; static { instance = new Singleton(); } private Singleton () {} public static Singleton getInstance() { return instance; }*/ /** * 第五種(靜態內部類) * 這種方式同樣利用了classloder的機制來保證初始化instance時只有一個執行緒, * 它跟第三種和第四種方式不同的是(很細微的差別): * 第三種和第四種方式是隻要Singleton類被裝載了,那麼instance就會被例項化(沒有達到lazy loading效果), * 而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用, * 只有顯示通過呼叫getInstance方法時,才會顯示裝載SingletonHolder類,從而例項化instance。 * 想象一下,如果例項化instance很消耗資源,我想讓他延遲載入, * 另外一方面,我不希望在Singleton類載入時就例項化, * 因為我不能確保Singleton類還可能在其他的地方被主動使用從而被載入, * 那麼這個時候例項化instance顯然是不合適的。 * 這個時候,這種方式相比第三和第四種方式就顯得很合理。 */ /*private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton () {} public static Singleton getInstance () { return SingletonHolder.INSTANCE; }*/ /** * 第六種(列舉) * 這種方式是Effective Java作者Josh Bloch 提倡的方式, * 它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘啊, * 在深度分析Java的列舉型別—-列舉的執行緒安全性及序列化問題中有詳細介紹列舉的執行緒安全問題和序列化問題, * 不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這麼寫過。 */ /*public enum Singleton(){ INSTANCE; public void whateverMethod(){ } }*/ /** * 第七種(雙重校驗鎖): * */ private volatile static Singleton instance; private Singleton () {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } /** * 總結 有兩個問題需要注意: 1.如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的例項。 假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器, 這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的例項。 2.如果Singleton實現了java.io.Serializable介面,那麼這個類的例項就可能被序列化和復原。 不管怎樣,如果你序列化一個單例類的物件,接下來複原多個那個物件,那你就會有多個單例類的例項。 對第一個問題修復的辦法是: private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(classname)); } } 對第二個問題修復的辦法是: public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { } private Object readResolve() { return INSTANCE; } } 對我來說,我比較喜歡第三種和第五種方式, 簡單易懂,而且在JVM層實現了執行緒安全(如果不是多個類載入器環境), 一般的情況下,我會使用第三種方式,只有在要明確實現lazy loading效果時才會使用第五種方式, 另外,如果涉及到反序列化建立物件時我會試著使用列舉的方式來實現單例, 不過,我一直會保證我的程式是執行緒安全的,而且我永遠不會使用第一種和第二種方式, 如果有其他特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。 不過一般來說,第一種不算單例,第四種和第三種就是一種,如果算的話,第五種也可以分開寫了。 所以說,一般單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,列舉和靜態內部類。 */ }