摘要:雙重檢查鎖單例模式懶漢單例模式中,我們并不需要整個方法都是同步的,我們只需要確保再創(chuàng)建的時候,進行同步即可。單例模式的缺點優(yōu)點在開頭已經(jīng)說明了,單例模式的缺點在于它一般沒有接口,擴展困難,基本上修改源代碼是擴展單例模式的唯一方法。
單例模式 定義:
確保某一個類只有一個實例對象,并且該對象是自行實例化的,通過統(tǒng)一的接口向整個系統(tǒng)提供這個實例對象。
使用場景:避免產(chǎn)生多個對象消耗過多的資源(比如該對象需要用到IO,Database等等),或者某個類的實例化對象應該只有一個的情況。
因為內(nèi)存中只有一個實例對象的存在,減少了內(nèi)存開支,同時,如果該對象的產(chǎn)生需要較多資源的時候(內(nèi)部需要依賴其他對象...),我們可以采取只生成一個對象,然后讓這個對象永久駐留在內(nèi)存中的方式實現(xiàn)。
如果需要定義大量的靜態(tài)常量和靜態(tài)方法,也可以采用單例模式實現(xiàn)。
關(guān)鍵點:1.構(gòu)造函數(shù)不對外開放,一般為private。
2.通過一個static方法或者枚舉返回給外部單例對象。
3.在多線程的條件下也能保證只有一個單例對象。
4.確保單例類對象再反序列化的時候不會創(chuàng)建新的對象。
實現(xiàn)方式: 1.餓漢單例模式public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
優(yōu)點:實現(xiàn)簡單,在類加載的時候完成了初始化工作,避免了多線程同步問題。
缺點:沒有實現(xiàn)懶加載,如果這個單例對象沒有被使用過,但是對應的類卻加載到內(nèi)存中的話,也會白白的占用不必要的內(nèi)存。
2.懶漢單例模式public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
優(yōu)點:實現(xiàn)了懶加載,在用到單例對象的時候再對其進行初始化,一定程度上節(jié)約了資源。
缺點:getInstance掛了一把鎖,每次獲取這個單例對象都需要同步,不管是不是并發(fā)情況下,都會早成不必要的同步開銷。
3.DCL雙重檢查鎖單例模式懶漢單例模式中,我們并不需要整個getInstance方法都是同步的,我們只需要確保再instance創(chuàng)建的時候,進行同步即可。
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ instance = new Singleton(); } } return instance; } }
優(yōu)點:線程安全,懶加載,執(zhí)行效率高,只有在instance為null的時候才會有同步開銷。
缺點:
Double-Checked Lock看起來是非常完美的。但是根據(jù)Java的語言規(guī)范,上面的代碼并非絕對可靠。
出現(xiàn)上述問題, 最重要的2個原因如下:1, 編譯器優(yōu)化了程序指令, 以加快cpu處理速度.
2, 多核cpu動態(tài)調(diào)整指令順序,允許指令亂序執(zhí)行, 以加快并行運算能力.問題出現(xiàn)的順序:
1, 線程A, 發(fā)現(xiàn)對象未實例化, 準備開始實例化2, 由于編譯器優(yōu)化了程序指令, 允許對象在構(gòu)造函數(shù)未調(diào)用完前, 將共享變量的引用指向部分構(gòu)造的對象, 雖然對象未完全實例化, 但已經(jīng)不為null了.
3, 線程B, 發(fā)現(xiàn)部分構(gòu)造的對象已不是null, 則直接返回了該對象(此時它為null本應該先創(chuàng)建再返回卻直接返回了)。
通俗來說,如果線程A的指令發(fā)現(xiàn)instance為null,則會去執(zhí)行初始化的指令,初始化指令最終翻譯成匯編指令可能是如下三個部分:
①為內(nèi)存對象分配內(nèi)存②構(gòu)造函數(shù)初始化成員字段
③將創(chuàng)建的對象指定到分配的內(nèi)存空間中
如果123順序執(zhí)行是沒有問題的,但是可能存在132亂序執(zhí)行的情況,如果3執(zhí)行完成,CPU切換到了另一個線程,同樣執(zhí)行g(shù)etInstance方法去獲取單例對象,單例對象不為空,但是獲取到的對象確實不正確的。
這就是DCL失效問題。
改進的辦法是,為instance加上volatile修飾符,保證對其修改其它線程立即可見。
private volatile static Singleton instance = null;
雖然volatile又需要額外的性能開銷,但是相比安全性,這個開銷是值得的。
靜態(tài)內(nèi)部類單例模式public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.sInstance; } private static class SingletonHolder{ private static final Singleton sInstance = new Singleton(); } }
根據(jù)類加載機制,對于內(nèi)部類而言,只有再需要的時候才會加載,也就是說位于SingletonHolder中的sInstance只有在第一次調(diào)用到getInstance的時候,才會被創(chuàng)建,從而既實現(xiàn)了懶加載,也能夠確保線程安全(由JVM確保,在類加載的時候,只有一個線程會執(zhí)行類加載動作,也就是創(chuàng)建單例對象只會由一個線程完成),推薦使用。
枚舉單例public class EnumSingleton{ private EnumSingleton(){} public static EnumSingleton getInstance(){ return Singleton.INSTANCE.getInstance(); } private static enum Singleton{ INSTANCE; private EnumSingleton singleton; // 在加載的時候進行初始化,JVM保證該方法只會被調(diào)用一次。 private Singleton(){ singleton = new EnumSingleton(); } public EnumSingleton getInstance(){ return singleton; } } }
枚舉類和普通類是一樣的,但是不同的是枚舉實例的創(chuàng)建默認是線程安全的,并且在任何情況下都是只有一個實例對象存在,即便是序列化反序列化也是。
單例模式對(反)序列化的改進上面所有的單例模式,除了借助枚舉來實現(xiàn)外,都存在一個缺點,也就是第四個關(guān)鍵點,我們需要保證單例對象在序列化和反序列化中可以保證對象的一致性,也就是不能通過反序列化違反單例的系統(tǒng)中只存在一個唯一對象的規(guī)定。
當然,這個情況的前提是,我們的單例類實現(xiàn)了序列化接口。
通過類的readResolve函數(shù),開發(fā)人員可以控制反序列化過程,杜絕在反序列化的時候生成新對象:
public final class Singleton implements Serializable{ private static final long serialVersionUID = 0L; private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } private Object readResolve(){ return INSTANCE; } }
同樣的,該方法因為需要用到序列化,自然是要符合序列化的要求,即內(nèi)部字段也是要可序列化的。
我們將serialVersionUID置為fianl,是為了保證在修改了單例類的內(nèi)部情況的時候,反序列化也不會拋出InvalidClassException異常,只會將新修改的字段置為默認值。
單例模式的缺點:優(yōu)點在開頭已經(jīng)說明了,單例模式的缺點在于它一般沒有接口,擴展困難,基本上修改源代碼是擴展單例模式的唯一方法。再有,如果單例對象持有Context,很容易引發(fā)內(nèi)存泄露問題,所以一般是用ApplicationContext。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/74670.html
摘要:總結(jié)單例是運用頻率很高的模式,因為客戶端沒有高并發(fā)的情況,選擇哪種方式并不會有太大的影響,出于效率考慮,推薦使用和靜態(tài)內(nèi)部類實現(xiàn)單例模式。 單例模式介紹 單例模式是應用最廣的模式之一,也可能是很多人唯一會使用的設計模式。在應用單例模式時,單例對象的類必須保證只用一個實例存在。許多時候整個系統(tǒng)只需要一個全局對象,這樣有利于我么能協(xié)調(diào)整個系統(tǒng)整體的行為。 單例模式的使用場景 確保某個類有且...
摘要:不符合設計模式中的單一職責的概念。引入代理實現(xiàn)單例模式引入代理實現(xiàn)單例模式的特點我們負責管理單例的邏輯移到了代理類中。的單例模式對比在以上的代碼中實現(xiàn)的單例模式都混入了傳統(tǒng)面向?qū)ο笳Z言的特點。 聲明:這個系列為閱讀《JavaScript設計模式與開發(fā)實踐》 ----曾探@著一書的讀書筆記 1.單例模式的特點和定義 保證一個類僅有一個實例,并且提供一個訪問它的全局訪問點。 2.傳統(tǒng)面向?qū)?..
摘要:但是,這并不是采用單例的唯一原因。使用命名空間單例模式也被稱為模塊設計模式。函數(shù)內(nèi)部聲明了一些局部函數(shù)和或變量。緊隨函數(shù)聲明放置即可立即執(zhí)行外部函數(shù),并將所得的對象文字費賠給變量。 JavaScript設計模式-第一部分:單例模式、組合模式和外觀模式 設計模式是一些可靠的編程方式,有助于保證代碼更加易于維護、擴展及分離,所有設計模式在創(chuàng)建大型JavaScript應用程序時均不可或缺 單...
摘要:如果需要防范這種攻擊,請修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個實例時拋出異常。單例模式與單一職責原則有沖突。源碼地址參考文獻設計模式之禪 定義 單例模式是一個比較簡單的模式,其定義如下: 保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 或者 Ensure a class has only one instance, and provide a global point of ac...
摘要:在設計模式一書中,將單例模式稱作單件模式。通過關(guān)鍵字,來保證不會同時有兩個線程進入該方法的實例對象改善多線程問題為了符合大多數(shù)程序,很明顯地,我們需要確保單例模式能在多線程的情況下正常工作。 在《Head First 設計模式》一書中,將單例模式稱作單件模式。這里為了適應大環(huán)境,把它稱之為大家更熟悉的單例模式。 一、了解單例模式 1.1 什么是單例模式 單例模式確保一個類只有一個實例,...
閱讀 2449·2021-10-08 10:17
閱讀 1824·2021-09-06 15:02
閱讀 2539·2019-08-29 17:30
閱讀 2663·2019-08-29 13:24
閱讀 1522·2019-08-29 11:12
閱讀 3337·2019-08-28 17:52
閱讀 666·2019-08-26 11:30
閱讀 3577·2019-08-26 11:01