本篇內(nèi)容主要講解“DCL和Singleton模式的問(wèn)題怎么解決”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“DCL和Singleton模式的問(wèn)題怎么解決”吧!
創(chuàng)新互聯(lián)建站主要從事成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)瓊海,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):13518219792
在多線程的情況下Singleton模式會(huì)遇到不少問(wèn)題,一個(gè)簡(jiǎn)單的例子
1: class Singleton { 2: private static Singleton instance = null; 3: 4: public static Singleton instance() { 5: if (instance == null) { 6: instance = new Singleton(); 7: } 8: return instance; 9: } 10: }
假設(shè)這樣一個(gè)場(chǎng)景,有兩個(gè)線程調(diào)用Singleton.instance(),首先線程一判斷instance是否等于null,判斷完后一瞬間虛擬機(jī)把線程二調(diào)度為運(yùn)行線程,線程二再次判斷instance是否為null,然后創(chuàng)建一個(gè)Singleton實(shí)例,線程二的時(shí)間片用完后,線程一被喚醒,接下來(lái)它執(zhí)行的代碼依然是instance = new Singleton();
兩次調(diào)用返回了不同的對(duì)象,出現(xiàn)問(wèn)題了。
最簡(jiǎn)單的方法自然是在類被載入時(shí)就初始化這個(gè)對(duì)象:private static Singleton instance = new Singleton();
JLS(Java Language Specification)中規(guī)定了一個(gè)類只會(huì)被初始化一次,所以這樣做肯定是沒問(wèn)題的。
但是如果要實(shí)現(xiàn)延遲初始化(Lazy initialization),比如這個(gè)實(shí)例初始化時(shí)的參數(shù)要在運(yùn)行期才能確定,應(yīng)該怎么做呢?
依然有最簡(jiǎn)單的方法:使用synchronized關(guān)鍵字修飾初始化方法:
public synchronized static Singleton instance() { if (instance == null) { instance = new Singleton(); } return instance; }
這里有一個(gè)性能問(wèn)題:多個(gè)線程同時(shí)訪問(wèn)這個(gè)方法時(shí),會(huì)因?yàn)橥蕉鴮?dǎo)致每次只有一個(gè)線程運(yùn)行,影響程序性能。而事實(shí)上初始化完畢后只需要簡(jiǎn)單的返回instance的引用就行了。
雙檢測(cè)鎖定解決方案
DCL是一個(gè)“看似”有效的解決方法,先把對(duì)應(yīng)代碼放上來(lái)吧:
1 : class Singleton { 2 : private static Singleton instance = null ; 3 : 4 : public static Singleton instance() { 5 : if (instance == null ) { 6 : synchronized (this) { 7 : if (instance == null) 8 : instance = new Singleton(); 9 : } 10 : } 11 : return instance; 12 : } 13 : }
用JavaWorld上對(duì)應(yīng)文章的標(biāo)題來(lái)評(píng)論這種做法就是smart, but broken。來(lái)看原因:
Java編譯器為了提高程序性能會(huì)進(jìn)行指令調(diào)度,CPU在執(zhí)行指令時(shí)同樣出于性能會(huì)亂序執(zhí)行(至少現(xiàn)在用的大多數(shù)通用處理器都是out-of-order的),另外cache的存在也會(huì)改變數(shù)據(jù)回寫內(nèi)存時(shí)的順序[2]。JMM(Java Memory Model, 見[1])指出所有的這些優(yōu)化都是允許的,只要運(yùn)行結(jié)果和嚴(yán)格按順序執(zhí)行所得的結(jié)果一樣即可。
Java假設(shè)每個(gè)線程都跑在自己的處理器上,享有自己的內(nèi)存,和共享的主存交互。注意即使在單核上這種模型也是有意義的,考慮到cache和寄存器會(huì)保存部分臨時(shí)變量。理論上每個(gè)線程修改自己的內(nèi)存后,必須立即更新對(duì)應(yīng)的主存內(nèi)容。但是Java設(shè)計(jì)師們認(rèn)為這種約束會(huì)影響程序性能,他們?cè)囍鴦?chuàng)造了一套讓程序跑得更快、但又保證線程之間的交互與預(yù)期一致的內(nèi)存模型。
synchronized關(guān)鍵字便是其中一把利器。事實(shí)上,synchronized塊的實(shí)現(xiàn)和Linux中的信號(hào)量(semaphore)還是有區(qū)別的,前者過(guò)程中鎖的獲得和釋放都會(huì)都會(huì)引發(fā)一次Memory Barrier來(lái)強(qiáng)制線程本地內(nèi)存和主存之間的同步。通過(guò)這個(gè)機(jī)制,Java中的同步機(jī)制保證了synchronized塊中指令的原子性(atomic)。
雙檢測(cè)鎖定的問(wèn)題
好了,回過(guò)頭來(lái)看DCL問(wèn)題。看起來(lái)訪問(wèn)一個(gè)未同步的instance字段不會(huì)產(chǎn)生什么問(wèn)題,我們?cè)俅蝸?lái)假設(shè)一個(gè)場(chǎng)景:
線程一進(jìn)入同步塊,執(zhí)行instance = new Singleton(); 線程二剛開始執(zhí)行g(shù)etResource();
按照順序的話,接下來(lái)應(yīng)該執(zhí)行的步驟是 1) 分配新的Singleton對(duì)象的內(nèi)存 2) 調(diào)用Singleton的構(gòu)造器,初始化成員字段 3) instance被賦為指向新的對(duì)象的引用。
前面說(shuō)過(guò),編譯器或處理器都為了提高性能都有可能進(jìn)行指令的亂序執(zhí)行,線程一的真正執(zhí)行步驟可能是1) 分配內(nèi)存 2) instance指向新對(duì)象 3) 初始化新實(shí)例。如果線程二在2完成后3執(zhí)行前被喚醒,它看到了一個(gè)不為null的instance,跳出方法體走了,帶著一個(gè)還沒初始化的Singleton對(duì)象。
錯(cuò)誤發(fā)生的一種情形就是這樣,關(guān)于更詳細(xì)的編譯器指令調(diào)度導(dǎo)致的問(wèn)題,可以參看這個(gè)網(wǎng)頁(yè) [4]。
[3] 中提供了一個(gè)編譯器指令調(diào)度的證據(jù)
instance = new Singleton(); 這條命令在Symantec JIT中被編譯成
0206106A mov eax,0F97E78h 0206106F call 01F6B210 ; 分配空間 02061074 mov dword ptr [ebp],eax ; EBP中保存了instance的地址 02061077 mov ecx,dword ptr [eax] ; 解引用,獲得新的指針地址 02061079 mov dword ptr [ecx],100h ; 接下來(lái)四行是inline后的構(gòu)造器 0206107F mov dword ptr [ecx+4],200h 02061086 mov dword ptr [ecx+8],400h 0206108D mov dword ptr [ecx+0Ch],0F84030h
可以看到,賦值完成在初始化之前,而這是JLS允許的。
另一種情形是,假設(shè)線程一安穩(wěn)地完成Singleton對(duì)象的初始化,退出了同步塊,并同步了和本地內(nèi)存和主存。線程二來(lái)了,看到一個(gè)非空的引用,拿走。注意線程二沒有執(zhí)行一個(gè)Read Barrier,因?yàn)樗揪蜎]進(jìn)后面的同步塊。所以很有可能此時(shí)它看到的數(shù)據(jù)是陳舊的。
還有很多人根據(jù)已知的幾種提出了一個(gè)又一個(gè)fix的方法,但最終還是出現(xiàn)了更多的問(wèn)題??梢詤㈤哰3]中的介紹。
[5]中還說(shuō)明了即使把instance字段聲明為volatile還是無(wú)法避免錯(cuò)誤的原因。
由此可見,安全的Singleton的構(gòu)造一般只有兩種方法,一是在類載入時(shí)就創(chuàng)建該實(shí)例,二是使用性能較差的synchronized方法。
到此,相信大家對(duì)“DCL和Singleton模式的問(wèn)題怎么解決”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!