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