3 回答

TA貢獻1775條經驗 獲得超8個贊
以下是第71項中建議的慣用語:明智地使用 Effective Java:
如果您需要使用延遲初始化來提高實例字段的性能,請使用double-check idiom。這種習慣用法避免了在初始化字段后訪問字段時發生鎖定的費用(項67)。習慣用語的想法是檢查字段的值兩次(因此,將其命名為double-check):一次不鎖定,然后,如果該字段似乎未初始化,則第二次鎖定。僅當第二次檢查表明該字段未初始化時,該調用才會初始化該字段。因為如果該字段已經初始化就沒有鎖定,所以聲明該字段至關重要volatile(項目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;
}
}
該代碼可能看起來有些混亂。特別是,對局部變量結果的需求可能不清楚。該變量的作用是確保在已初始化字段的常見情況下,該字段僅被讀取一次。盡管不是絕對必要的,但是這可以提高性能,并且通過應用于低級并發編程的標準可以更加優雅。在我的機器上,上述方法比不帶局部變量的明顯方法快25%。
在1.5版之前,由于volatile修飾符的語義不足以支持它,所以雙重檢查慣用語不能可靠地工作[Pugh01]。版本1.5中引入的內存模型解決了此問題[JLS,17,Goetz06 16]。如今,仔細檢查慣用語是延遲初始化實例字段的首選技術。雖然您也可以將雙重檢查慣用語應用于靜態字段,但沒有理由這樣做:惰性初始化持有人類慣用語是更好的選擇。
參考
有效的Java,第二版
項目71:明智地使用惰性初始化

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