摘要:如果是后者,則在執(zhí)行完畢未執(zhí)行之前,被線(xiàn)程二搶占了,這時(shí)已經(jīng)是非了但卻沒(méi)有初始化,所以線(xiàn)程二會(huì)直接返回在之后雙重檢查鎖定才能夠正常達(dá)到單例效果,之前有個(gè)坑。所以,在版本前,雙重檢查鎖形式的單例模式是無(wú)法保證線(xiàn)程安全的。
第一種(懶漢, 線(xiàn)程不安全):
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
這種寫(xiě)法 lazy loading 很明顯, 但是致命的是在多線(xiàn)程不能正常工作。
第二種(懶漢, 線(xiàn)程安全):
public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
這種寫(xiě)法能夠在多線(xiàn)程中很好的工作, 而且看起來(lái)它也具備很好的 lazy loading, 但是, 遺憾的是, 效率很低, 99% 情況下不需要同步。
第三種(餓漢):
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
這種方式基于 classloder 機(jī)制避免了多線(xiàn)程的同步問(wèn)題, 不過(guò), instance 在類(lèi)裝載時(shí)就實(shí)例化, 雖然導(dǎo)致類(lèi)裝載的原因有很多種, 在單例模式中大多數(shù)都是調(diào)用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類(lèi)裝載, 這時(shí)候初始化 instance 顯然沒(méi)有達(dá)到 lazy loading 的效果。
第四種(餓漢, 變種):
public class Singleton { private Singleton instance = null; static { instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return this.instance; } }
表面上看起來(lái)差別挺大, 其實(shí)更第三種方式差不多, 都是在類(lèi)初始化即實(shí)例化 instance。
第五種(靜態(tài)內(nèi)部類(lèi)):
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
這種方式同樣利用了classloder的機(jī)制來(lái)保證初始化instance時(shí)只有一個(gè)線(xiàn)程, 它跟第三種和第四種方式不同的是(很細(xì)微的差別):第三種和第四種方式是只要Singleton類(lèi)被裝載了, 那么instance就會(huì)被實(shí)例化(沒(méi)有達(dá)到lazy loading效果), 而這種方式是Singleton類(lèi)被裝載了, instance不一定被初始化。因?yàn)镾ingletonHolder類(lèi)沒(méi)有被主動(dòng)使用, 只有顯示通過(guò)調(diào)用getInstance方法時(shí), 才會(huì)顯示裝載SingletonHolder類(lèi), 從而實(shí)例化instance。想象一下, 如果實(shí)例化instance很消耗資源, 我想讓他延遲加載, 另外一方面, 我不希望在Singleton類(lèi)加載時(shí)就實(shí)例化, 因?yàn)槲也荒艽_保Singleton類(lèi)還可能在其他的地方被主動(dòng)使用從而被加載, 那么這個(gè)時(shí)候?qū)嵗痠nstance顯然是不合適的。這個(gè)時(shí)候, 這種方式相比第三和第四種方式就顯得很合理。
第六種(枚舉):
public enum Singleton { INSTANCE; public void whateverMethod() { } }
這種方式是 Effective Java作者 Josh Bloch 提倡的方式, 它不僅能避免多線(xiàn)程同步問(wèn)題, 而且還能防止反序列化重新創(chuàng)建新的對(duì)象, 可謂是很堅(jiān)強(qiáng)的壁壘啊, 不過(guò), 個(gè)人認(rèn)為由于 1.5 中才加入 enum 特性, 用這種方式寫(xiě)不免讓人感覺(jué)生疏, 在實(shí)際工作中, 我也很少看見(jiàn)有人這么寫(xiě)過(guò)。
第七種(雙重校驗(yàn)鎖):
public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
這個(gè)是第二種方式的升級(jí)版, 俗稱(chēng)雙重檢查鎖定, 也有瑕疵。
主要在于singleton = new Singleton()這句,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話(huà)大概做了下面 3 件事情。
給 singleton 分配內(nèi)存
調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量,形成實(shí)例
將singleton對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null 了)
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說(shuō)上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線(xiàn)程二搶占了,這時(shí) instance 已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線(xiàn)程二會(huì)直接返回 instance.
在JDK1.5之后, 雙重檢查鎖定才能夠正常達(dá)到單例效果,1.5之前有個(gè)坑。
說(shuō)這個(gè)坑之前我們要先來(lái)看看volatile這個(gè)關(guān)鍵字。其實(shí)這個(gè)關(guān)鍵字有兩層語(yǔ)義。第一層語(yǔ)義相信大家都比較熟悉,就是可見(jiàn)性??梢?jiàn)性指的是在一個(gè)線(xiàn)程中對(duì)該變量的修改會(huì)馬上由工作內(nèi)存(Work Memory)寫(xiě)回主內(nèi)存(Main Memory),所以會(huì)馬上反應(yīng)在其它線(xiàn)程的讀取操作中。順便一提,工作內(nèi)存和主內(nèi)存可以近似理解為實(shí)際電腦中的高速緩存和主存,工作內(nèi)存是線(xiàn)程獨(dú)享的,主存是線(xiàn)程共享的。volatile的第二層語(yǔ)義是禁止指令重排序優(yōu)化。大家知道我們寫(xiě)的代碼(尤其是多線(xiàn)程代碼),由于編譯器優(yōu)化,在實(shí)際執(zhí)行的時(shí)候可能與我們編寫(xiě)的順序不同。編譯器只保證程序執(zhí)行結(jié)果與源代碼相同,卻不保證實(shí)際指令的順序與源代碼相同。這在單線(xiàn)程看起來(lái)沒(méi)什么問(wèn)題,然而一旦引入多線(xiàn)程,這種亂序就可能導(dǎo)致嚴(yán)重問(wèn)題。volatile關(guān)鍵字就可以從語(yǔ)義上解決這個(gè)問(wèn)題。
但是很不幸,禁止指令重排優(yōu)化這條語(yǔ)義直到j(luò)dk1.5以后才能正確工作。此前的JDK中即使將變量聲明為volatile也無(wú)法完全避免重排序所導(dǎo)致的問(wèn)題。所以,在jdk1.5版本前,雙重檢查鎖形式的單例模式是無(wú)法保證線(xiàn)程安全的。
有兩個(gè)問(wèn)題需要注意:
如果單例由不同的類(lèi)裝載器裝入, 那便有可能存在多個(gè)單例類(lèi)的實(shí)例。假定不是遠(yuǎn)端存取, 例如一些servlet容器對(duì)每個(gè)servlet使用完全不同的類(lèi) 裝載器, 這樣的話(huà)如果有兩個(gè)servlet訪問(wèn)一個(gè)單例類(lèi), 它們就都會(huì)有各自的實(shí)例。
如果 Singleton 實(shí)現(xiàn)了 java.io.Serializable 接口, 那么這個(gè)類(lèi)的實(shí)例就可能被序列化和復(fù)原。不管怎樣, 如果你序列化一個(gè)單例類(lèi)的對(duì)象, 接下來(lái)復(fù)原多個(gè)那個(gè)對(duì)象, 那你就會(huì)有多個(gè)單例類(lèi)的實(shí)例。
對(duì)第一個(gè)問(wèn)題修復(fù)的辦法:
private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { classLoader = Singleton.class.getClassLoader(); } return (classLoader.loadClass(classname)); }
對(duì)第二個(gè)問(wèn)題修復(fù)的辦法:
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { } private Object readResolve() { return INSTANCE; } }
對(duì)我來(lái)說(shuō), 我比較喜歡第三種和第五種方式, 簡(jiǎn)單易懂, 而且在JVM層實(shí)現(xiàn)了線(xiàn)程安全(如果不是多個(gè)類(lèi)加載器環(huán)境), 一般的情況下, 我會(huì)使用第三種方式, 只有在要明確實(shí)現(xiàn)lazy loading效果時(shí)才會(huì)使用第五種方式, 另外, 如果涉及到反序列化創(chuàng)建對(duì)象時(shí)我會(huì)試著使用枚舉的方式來(lái)實(shí)現(xiàn)單例, 不過(guò), 我一直會(huì)保證我的程序是線(xiàn)程安全的, 而且我永遠(yuǎn)不會(huì)使用第一種和第二種方式, 如果有其他特殊的需求, 我可能會(huì)使用第七種方式, 畢竟, JDK1.5已經(jīng)沒(méi)有雙重檢查鎖定的問(wèn)題了。
不過(guò)一般來(lái)說(shuō), 第一種不算單例, 第四種和第三種就是一種, 如果算的話(huà), 第五種也可以分開(kāi)寫(xiě)了。所以說(shuō), 一般單例都是五種寫(xiě)法。懶漢, 惡漢, 雙重校驗(yàn)鎖, 枚舉和靜態(tài)內(nèi)部類(lèi)。
線(xiàn)程安全
延遲加載
序列化與反序列化安全
除了枚舉形式, 其他實(shí)現(xiàn)方式都有兩個(gè)共同的缺點(diǎn)
都需要額外的工作(Serializable、transient、readResolve())來(lái)實(shí)現(xiàn)序列化,否則每次反序列化一個(gè)序列化的對(duì)象實(shí)例時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例。
可能會(huì)有人使用反射強(qiáng)行調(diào)用我們的私有構(gòu)造器(如果要避免這種情況,可以修改構(gòu)造器,讓它在創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋異常)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/66861.html
時(shí)間:2017年08月27日星期日說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:https://github.com/zccodere/s...學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:?jiǎn)卫J胶?jiǎn)介 1-1 簡(jiǎn)介 單例模式 概念及應(yīng)用場(chǎng)合 餓漢模式 懶漢模式 餓漢模式與懶漢模式的區(qū)別 什么是設(shè)計(jì)模式 是一套被反...
摘要:在設(shè)計(jì)模式一書(shū)中,將單例模式稱(chēng)作單件模式。通過(guò)關(guān)鍵字,來(lái)保證不會(huì)同時(shí)有兩個(gè)線(xiàn)程進(jìn)入該方法的實(shí)例對(duì)象改善多線(xiàn)程問(wèn)題為了符合大多數(shù)程序,很明顯地,我們需要確保單例模式能在多線(xiàn)程的情況下正常工作。 在《Head First 設(shè)計(jì)模式》一書(shū)中,將單例模式稱(chēng)作單件模式。這里為了適應(yīng)大環(huán)境,把它稱(chēng)之為大家更熟悉的單例模式。 一、了解單例模式 1.1 什么是單例模式 單例模式確保一個(gè)類(lèi)只有一個(gè)實(shí)例,...
摘要:總結(jié)單例是運(yùn)用頻率很高的模式,因?yàn)榭蛻?hù)端沒(méi)有高并發(fā)的情況,選擇哪種方式并不會(huì)有太大的影響,出于效率考慮,推薦使用和靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例模式。 單例模式介紹 單例模式是應(yīng)用最廣的模式之一,也可能是很多人唯一會(huì)使用的設(shè)計(jì)模式。在應(yīng)用單例模式時(shí),單例對(duì)象的類(lèi)必須保證只用一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要一個(gè)全局對(duì)象,這樣有利于我么能協(xié)調(diào)整個(gè)系統(tǒng)整體的行為。 單例模式的使用場(chǎng)景 確保某個(gè)類(lèi)有且...
摘要:在面向?qū)ο蟮恼Z(yǔ)言中,比如,等,單例模式通常是定義類(lèi)時(shí)將構(gòu)造函數(shù)設(shè)為,保證對(duì)象不能在外部被出來(lái),同時(shí)給類(lèi)定義一個(gè)靜態(tài)的方法,用來(lái)獲取或者創(chuàng)建這個(gè)唯一的實(shí)例。 萬(wàn)事開(kāi)頭難,作為正經(jīng)歷菜鳥(niǎo)賽季的前端player,已經(jīng)忘記第一次告訴自己要寫(xiě)一些東西出來(lái)是多久以的事情了。。。如果,你也和我一樣,那就像我一樣,從現(xiàn)在開(kāi)始,從看到這篇文章開(kāi)始,打開(kāi)電腦,敲下你的第一篇文章(或者任何形式的文字)吧。 ...
閱讀 770·2021-11-18 10:02
閱讀 2305·2021-11-15 18:13
閱讀 3316·2021-11-15 11:38
閱讀 3078·2021-09-22 15:55
閱讀 3745·2021-08-09 13:43
閱讀 2521·2021-07-25 14:19
閱讀 2523·2019-08-30 14:15
閱讀 3510·2019-08-30 14:15