首先要解釋一下什么是延遲加載,延遲加載就是等到真真使用的時(shí)候才去創(chuàng)建實(shí)例,不用時(shí)不要去創(chuàng)建。
從速度和反應(yīng)時(shí)間角度來(lái)講,非延遲加載(又稱餓漢式)好;從資源利用效率上說(shuō),延遲加載(又稱懶漢式)好。
下面看看幾種常見(jiàn)的單例的設(shè)計(jì)方式:
第一種:非延遲加載單例類
public class Singleton {private Singleton() {}private static final Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}}
第二種:同步延遲加載
public class Singleton {private static Singleton instance = null;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
第三種:雙重檢測(cè)同步延遲加載
為處理原版非延遲加載方式瓶頸問(wèn)題,我們需要對(duì) instance 進(jìn)行第二次檢查,目的是避開(kāi)過(guò)多的同步(因?yàn)檫@里的同步只需在第一次創(chuàng)建實(shí)例時(shí)才同步,一旦創(chuàng)建成功,以后獲取實(shí)例時(shí)就不需要同獲取鎖了),但在Java中行不通,因?yàn)橥綁K外面的if (instance == null)可能看到已存在,但不完整的實(shí)例。JDK5.0以后版本若instance為volatile則可行:
public class Singleton {private volatile static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {// 1if (instance == null) {// 2instance = new Singleton();// 3}}}return instance;}}
雙重檢測(cè)鎖定失敗的問(wèn)題并不歸咎于 JVM 中的實(shí)現(xiàn) bug,而是歸咎于 Java 平臺(tái)內(nèi)存模型。內(nèi)存模型允許所謂的“無(wú)序?qū)懭?#8221;,這也是失敗的一個(gè)主要原因。
如果真像這篇文章:http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html所說(shuō)那樣的話,1.2或以后的版本就不會(huì)有問(wèn)題了,但這個(gè)規(guī)則是JMM的規(guī)范嗎?誰(shuí)能夠確認(rèn)一下。
確實(shí),在JAVA2(以jdk1.2開(kāi)始)以前對(duì)于實(shí)例字段是直接在主儲(chǔ)區(qū)讀寫(xiě)的.所以當(dāng)一個(gè)線程對(duì)resource進(jìn)行分配空間,
初始化和調(diào)用構(gòu)造方法時(shí),可能在其它線程中分配空間動(dòng)作可見(jiàn)了,而初始化和調(diào)用構(gòu)造方法還沒(méi)有完成.
但是從JAVA2以后,JMM發(fā)生了根本的改變,分配空間,初始化,調(diào)用構(gòu)造方法只會(huì)在線程的工作存儲(chǔ)區(qū)完成,在沒(méi)有
向主存儲(chǔ)區(qū)復(fù)制賦值時(shí),其它線程絕對(duì)不可能見(jiàn)到這個(gè)過(guò)程.而這個(gè)字段復(fù)制到主存區(qū)的過(guò)程,更不會(huì)有分配空間后
沒(méi)有初始化或沒(méi)有調(diào)用構(gòu)造方法的可能.在JAVA中,一切都是按引用的值復(fù)制的.向主存儲(chǔ)區(qū)同步其實(shí)就是把線程工作
存儲(chǔ)區(qū)的這個(gè)已經(jīng)構(gòu)造好的對(duì)象有壓縮堆地址值COPY給主存儲(chǔ)區(qū)的那個(gè)變量.這個(gè)過(guò)程對(duì)于其它線程,要么是resource
為null,要么是完整的對(duì)象.絕對(duì)不會(huì)把一個(gè)已經(jīng)分配空間卻沒(méi)有構(gòu)造好的對(duì)象讓其它線程可見(jiàn).
另一篇詳細(xì)分析文章:http://www.javaeye.com/topic/260515
第四種:使用ThreadLocal修復(fù)雙重檢測(cè)
借助于ThreadLocal,將臨界資源(需要同步的資源)線程局部化,具體到本例就是將雙重檢測(cè)的第一層檢測(cè)條件 if (instance == null) 轉(zhuǎn)換為了線程局部范圍內(nèi)來(lái)作。這里的ThreadLocal也只是用作標(biāo)示而已,用來(lái)標(biāo)示每個(gè)線程是否已訪問(wèn)過(guò),如果訪問(wèn)過(guò),則不再需要走同步塊,這樣就提高了一定的效率。但是ThreadLocal在1.4以前的版本都較慢,但這與volatile相比卻是安全的。
public class Singleton {private static final ThreadLocal perThreadInstance = new ThreadLocal();private static Singleton singleton ;private Singleton() {}public static Singleton getInstance() {if (perThreadInstance.get() == null){// 每個(gè)線程第一次都會(huì)調(diào)用createInstance();}return singleton;}private static final void createInstance() {synchronized (Singleton.class) {if (singleton == null){singleton = new Singleton();}}perThreadInstance.set(perThreadInstance);}}
第五種:使用內(nèi)部類實(shí)現(xiàn)延遲加載
為了做到真真的延遲加載,雙重檢測(cè)在Java中是行不通的,所以只能借助于另一類的類加載加延遲加載:
public class Singleton {private Singleton() {}public static class Holder {// 這里的私有沒(méi)有什么意義/* private */static Singleton instance = new Singleton();}public static Singleton getInstance() {// 外圍類能直接訪問(wèn)內(nèi)部類(不管是否是靜態(tài)的)的私有變量return Holder.instance;}}
單例測(cè)試
下面是測(cè)試單例的框架,采用了類加載器與反射。
注,為了測(cè)試單便是否為真真的單例,我自己寫(xiě)了一個(gè)類加載器,且其父加載器設(shè)置為根加載器,這樣確保Singleton由MyClassLoader加載,如果不設(shè)置為根加載器為父加載器,則默認(rèn)為系統(tǒng)加載器,則Singleton會(huì)由系統(tǒng)加載器去加載,但這樣我們無(wú)法卸載類加載器,如果加載Singleton的類加載器卸載不掉的話,那么第二次就不能重新加載Singleton的Class了,這樣Class不能得加載則最終導(dǎo)致Singleton類中的靜態(tài)變量重新初始化,這樣就無(wú)法測(cè)試了。
下面測(cè)試類延遲加載的結(jié)果是可行的,同樣也可用于其他單例的測(cè)試:
聯(lián)系客服