介紹 — Java 中的線程優化
SunIBMBEA和其他公司在各自實現的Java 虛擬機上都花費了大量的精力優化鎖的管理和同步諸如偏向鎖(biased locking)鎖粗化(lock coarsening)由逸出(escape)分析產生的鎖省略自適應自旋鎖(adaptive spinning)這些特性都是通過在應用程序線程之間更高效地共享數據從而提高並發效率盡管這些特性都是成熟且有趣的但是問題在於它們的承諾真的能實現麼?在這篇由兩部分組成的文章裡我將逐一探究這些特性並嘗試在單一線程基准的協助下回答關於性能的問題
悲觀鎖模型
Java支持的鎖模型絕對是悲觀鎖(其實大多數線程庫都是如此)如果有兩個或者更多線程使用數據時會彼此干擾這種極小的風險也會強迫我們采用非常嚴厲的手段防止這種情況的發生——使用鎖然而研究表明鎖很少被占用也就是說一個訪問鎖的線程很少必須等待來獲取它但是請求鎖的動作將會觸發一系列的動作這可能導致嚴重的系統開銷這是不可避免的
我們的確還有其他的選擇舉例來說考慮一下線程安全的StringBuffer的用法問問你自己是否你曾經明知道它只能被一個線程安全地訪問還是堅持使用StringBuffer為什麼不用StringBuilder代替呢?
知道大多數的鎖都不存在競爭或者很少存在競爭的事實對我們作用並不大因為即使是兩個線程訪問相同數據的概率非常低也會強迫我們使用鎖通過同步來保護被訪問的數據我們真的需要鎖麼?這個問題只有在我們將鎖放在運行時環境的上下文中觀察之後才能最終給出答案為了找到問題的答案JVM的開發者已經開始在HotSpot和JIT上進行了很多的實驗性的工作現在我們已經從這些工作中獲得了自適應自旋鎖偏向鎖和以及兩種方式的鎖消除(lock elimination)——鎖粗化和鎖省略(lock elision)在我們開始進行基准測試以前先來花些時間回顧一下這些特性這樣有助於理解它們是如何工作的
逸出分析 — 簡析鎖省略(Escape analysis lock elision explained)
逸出分析是對運行中的應用程序中的全部引用的范圍所做的分析逸出分析是HotSpot分析工作的一個組成部分如果HotSpot(通過逸出分析)能夠判斷出指向某個對象的多個引用被限制在局部空間內並且所有這些引用都不能逸出到這個空間以外的地方那麼HotSpot會要求JIT進行一系列的運行時優化其中一種優化就是鎖省略(lock elision)如果鎖的引用限制在局部空間中說明只有創建這個鎖的線程才會訪問該鎖在這種條件下同步塊中的值永遠不會存在競爭這意味這我們永遠不可能真的需要這把鎖它可以被安全地忽略掉考慮下面的方法
publicString concatBuffer(String s
String s
String s
) {
StringBuffer sb = new StringBuffer();
sb
append(s
);
sb
append(s
);
sb
append(s
);
return sb
toString();
}
如果我們觀察變量sb很快就會發現它僅僅被限制在concatBuffer方法內部了進一步說到sb的所有引用永遠不會逸出到 concatBuffer方法之外即聲明它的那個方法因此其他線程無法訪問當前線程的sb副本根據我們剛介紹的知識我們知道用於保護sb的鎖可以忽略掉
從表面上看鎖省略似乎可以允許我們不必忍受同步帶來的負擔就可以編寫線程安全的代碼了前提是在同步的確是多余的情況下鎖省略是否真的能發揮作用呢?這是我們在後面的基准測試中將要回答的問題
簡析偏向鎖(Biased locking explained)
大多數鎖在它們的生命周期中從來不會被多於一個線程所訪問即使在極少數情況下多個線程真的共享數據了鎖也不會發生競爭為了理解偏向鎖的優勢我們首先需要回顧一下如何獲取鎖(監視器)
獲取鎖的過程分為兩部分首先你需要獲得一份契約一旦你獲得了這份契約就可以自由地拿到鎖了為了獲得這份契約線程必須執行一個代價昂貴的原子指令釋放鎖同時就要釋放契約根據我們的觀察我們似乎需要對一些鎖的訪問進行優化比如線程執行的同步塊代碼在一個循環體中優化的方法之一就是將鎖粗化以包含整個循環這樣線程只訪問一次鎖而不必每次進入循環時都進行訪問了但是這並非一個很好的解決方案因為它可能會妨礙其他線程合法的訪問還有一個更合理的方案即將鎖偏向給執行循環的線程
將鎖偏向於一個線程意味著該線程不需要釋放鎖的契約因此隨後獲取鎖的時候可以不那麼昂貴如果另一個線程在嘗試獲取鎖那麼循環線程只需要釋放契約就可以了Java 的HotSpot/JIT默認情況下實現了偏向鎖的優化
[] [] [] []
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27671.html