3 回答

TA貢獻(xiàn)1775條經(jīng)驗(yàn) 獲得超8個(gè)贊
以下是第71項(xiàng)中建議的慣用語:明智地使用 Effective Java:
如果您需要使用延遲初始化來提高實(shí)例字段的性能,請(qǐng)使用double-check idiom。這種習(xí)慣用法避免了在初始化字段后訪問字段時(shí)發(fā)生鎖定的費(fèi)用(項(xiàng)67)。習(xí)慣用語的想法是檢查字段的值兩次(因此,將其命名為double-check):一次不鎖定,然后,如果該字段似乎未初始化,則第二次鎖定。僅當(dāng)?shù)诙螜z查表明該字段未初始化時(shí),該調(diào)用才會(huì)初始化該字段。因?yàn)槿绻撟侄我呀?jīng)初始化就沒有鎖定,所以聲明該字段至關(guān)重要volatile(項(xiàng)目66)。這是成語:
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result != null) // First check (no locking)
return result;
synchronized(this) {
if (field == null) // Second check (with locking)
field = computeFieldValue();
return field;
}
}
該代碼可能看起來有些混亂。特別是,對(duì)局部變量結(jié)果的需求可能不清楚。該變量的作用是確保在已初始化字段的常見情況下,該字段僅被讀取一次。盡管不是絕對(duì)必要的,但是這可以提高性能,并且通過應(yīng)用于低級(jí)并發(fā)編程的標(biāo)準(zhǔn)可以更加優(yōu)雅。在我的機(jī)器上,上述方法比不帶局部變量的明顯方法快25%。
在1.5版之前,由于volatile修飾符的語義不足以支持它,所以雙重檢查慣用語不能可靠地工作[Pugh01]。版本1.5中引入的內(nèi)存模型解決了此問題[JLS,17,Goetz06 16]。如今,仔細(xì)檢查慣用語是延遲初始化實(shí)例字段的首選技術(shù)。雖然您也可以將雙重檢查慣用語應(yīng)用于靜態(tài)字段,但沒有理由這樣做:惰性初始化持有人類慣用語是更好的選擇。
參考
有效的Java,第二版
項(xiàng)目71:明智地使用惰性初始化

TA貢獻(xiàn)1873條經(jīng)驗(yàn) 獲得超9個(gè)贊
使用ThreadLocal的DCL作者:Brian Goetz @ JavaWorld
DCL有什么問題?
DCL依賴于資源字段的不同步使用。這似乎無害,但事實(shí)并非如此。要了解為什么,請(qǐng)想象線程A在同步塊內(nèi)部,執(zhí)行語句resource = new Resource();。而線程B剛剛進(jìn)入getResource()??紤]此初始化對(duì)內(nèi)存的影響。新資源對(duì)象的內(nèi)存將被分配;Resource的構(gòu)造函數(shù)將被調(diào)用,初始化新對(duì)象的成員字段;并且SomeClass的字段資源將被分配一個(gè)對(duì)新創(chuàng)建對(duì)象的引用。
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
但是,由于線程B不在同步塊內(nèi)執(zhí)行,因此它們看到這些內(nèi)存操作的順序可能與一個(gè)線程A的執(zhí)行順序不同。B可能會(huì)按照以下順序看到這些事件(并且編譯器還可以自由地重新排列這樣的指令):分配內(nèi)存,分配對(duì)資源的引用,調(diào)用構(gòu)造函數(shù)。假設(shè)線程B在分配了內(nèi)存并設(shè)置了資源字段之后但在調(diào)用構(gòu)造函數(shù)之前出現(xiàn)。它看到資源不為空,跳過同步塊,并返回對(duì)部分構(gòu)造的Resource的引用!不用說,結(jié)果既不是預(yù)期的也不是期望的。
ThreadLocal可以幫助修復(fù)DCL嗎?
我們可以使用ThreadLocal來實(shí)現(xiàn)DCL習(xí)慣用法的明確目標(biāo)-延遲初始化,而無需在公共代碼路徑上進(jìn)行同步??紤]以下(線程安全)DCL版本:
清單2.使用ThreadLocal的DCL
class ThreadLocalDCL {
private static ThreadLocal initHolder = new ThreadLocal();
private static Resource resource = null;
public Resource getResource() {
if (initHolder.get() == null) {
synchronized {
if (resource == null)
resource = new Resource();
initHolder.set(Boolean.TRUE);
}
}
return resource;
}
}
我認(rèn)為; 這里每個(gè)線程將一次進(jìn)入SYNC塊以更新threadLocal值;那么它不會(huì)。因此,ThreadLocal DCL將確保線程僅在SYNC塊內(nèi)進(jìn)入一次。
同步到底是什么意思?
Java將每個(gè)線程視為在其具有自己的本地內(nèi)存的處理器上運(yùn)行,每個(gè)線程都與共享的主內(nèi)存通信并與之同步。即使在單處理器系統(tǒng)上,由于內(nèi)存高速緩存的影響以及使用處理器寄存器存儲(chǔ)變量的影響,該模型還是有意義的。當(dāng)線程修改其本地內(nèi)存中的位置時(shí),該修改最終還將顯示在主內(nèi)存中,并且JMM定義了JVM必須何時(shí)在本地和主內(nèi)存之間傳輸數(shù)據(jù)的規(guī)則。Java架構(gòu)師意識(shí)到,過于嚴(yán)格的內(nèi)存模型會(huì)嚴(yán)重破壞程序性能。他們?cè)噲D設(shè)計(jì)一種內(nèi)存模型,該內(nèi)存模型將使程序在現(xiàn)代計(jì)算機(jī)硬件上運(yùn)行良好,同時(shí)仍提供保證,以允許線程以可預(yù)測的方式進(jìn)行交互。
Java可預(yù)測地呈現(xiàn)線程之間的交互的主要工具是synced關(guān)鍵字。許多程序員在強(qiáng)制執(zhí)行互斥信號(hào)量(mutex)時(shí)嚴(yán)格考慮同步,以防止一次由多個(gè)線程執(zhí)行關(guān)鍵部分。不幸的是,這種直覺不能完全描述同步的含義。
同步的語義確實(shí)的確包括基于信號(hào)量狀態(tài)的相互排斥執(zhí)行,但是它們還包括有關(guān)同步線程與主內(nèi)存交互的規(guī)則。特別是,獲取或釋放鎖會(huì)觸發(fā)內(nèi)存屏障,即線程的本地內(nèi)存和主內(nèi)存之間的強(qiáng)制同步。(某些處理器(例如Alpha)具有用于執(zhí)行內(nèi)存屏障的顯式機(jī)器指令。)當(dāng)線程退出同步塊時(shí),它將執(zhí)行寫屏障-在釋放之前,必須將在該塊中修改的所有變量清除到主內(nèi)存中鎖。同樣,進(jìn)入同步塊時(shí),它執(zhí)行讀取屏障操作-好像本地存儲(chǔ)器已失效,并且它必須從主存儲(chǔ)器中獲取將在該塊中引用的所有變量。
添加回答
舉報(bào)