簡析鎖粗化(Lock coarsening explained)
另一種線程優化方式是鎖粗化(或合並merging)當多個彼此靠近的同步塊可以合並到一起形成一個同步塊的時候就會進行鎖粗化該方法還有一種變體可以把多個同步方法合並為一個方法如果所有方法都用一個鎖對象就可以嘗試這種方法考慮圖中的實例
public static String concatToBuffer(StringBuffer sb String s String s String s) {
sbappend(s);
sbappend(s);
sbappend(s);
return
}
在這個例子中StringBuffer的作用域是非局部的可以被多個線程訪問所以逸出分析會判斷出StringBuffer的鎖不能安全地被忽略如果鎖剛好只被一個線程訪問則可以使用偏向鎖有趣的是是否進行鎖粗化與競爭鎖的線程數量是無關的在上面的例子中鎖的實例會被請求四次前三次是執行append方法最後一次是執行toString方法緊接著前一個首先要做的是將這種方法進行內聯然後我們只需執行一次獲取鎖的操作(為整個方法)而不必像以前一樣獲取四次鎖了
這種做法帶來的真正效果是我們獲得了一個更長的臨界區它可能導致其他線程受到拖延從而降低吞吐量正因為這些原因一個處於循環內部的鎖是不會被粗化到包含整個循環體的
線程掛起 vs 自旋(Thread suspending versus spinning)
在一個線程等待另外一個線程釋放某個鎖的時候它通常會被操作系統掛起操作在掛起一個線程的時候需要將它換出CPU而通常此時線程的時間片還沒有使用完當擁有鎖的線程離開臨界區的時候掛起的線程需要被重新喚醒然後重新被調用並交換上下文回到CPU調度中所有這些動作都會給JVMOS和硬件帶來更大的壓力
在這個例子中如果注意到下面的事實會很有幫助鎖通常只會被占有很短的一段時間這就是說如果能夠等上一會兒我們可以避免掛起線程的開銷為了讓線程等待我們只需將線程執行一個忙循環(自旋)這項技術就是所謂的自旋鎖
當鎖被占有的時間很短時自旋鎖的效果非常好另一方面如果鎖被占有很長時間那麼自旋的線程只會消耗CPU而不做任何有用的工作因此帶來浪費自從JDK 中引入自旋鎖以來自旋鎖被分為兩個階段自旋十個循環(默認值)然後掛起線程
自適應自旋鎖(Adaptive spinning)
JDK 中引入了自適應自旋鎖自適應意味著自旋的時間不再固定了而是取決於一個基於前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態如果在同一個鎖對象上自旋剛剛成功過並且持有鎖的線程正在運行中那麼自旋很有可能再次成功進而它將被應用於相對更長的時間比如個循環另一方面如果自旋很少發生過它將被遺棄避免浪費任何CPU周期
StringBuffer vs StringBuilder的基准測試
但是要想設計出一種方法來判斷這些巧妙的優化方法到底多有效這條路並不平坦首要的問題就是如何設計基准測試為了找到問題的答案我決定去看看人們通常在代碼中運用了哪些常見的技巧我首先想到的是一個非常古老的問題使用StringBuffer代替String可以減少多少開銷?
一個類似的建議是如果你希望字符串是可變的就應該使用StringBuffer這個建議的緣由是非常明確的String是不可變的但如果我們的工作需要字符串有很多變化StringBuffer將是一個開銷較低的選擇有趣的是在遇到JDK 中的StringBuilder(它是StringBuffer的非同步版本)後這條建議就不靈了由於StringBuilder與 StringBuffer之間唯一的不同在於同步性這似乎說明測量兩者之間性能差異的基准測試必須關注在同步的開銷上我們的探索從第一個問題開始非競爭鎖的開銷如何?
這個基准測試的關鍵(如清單所示)在於將大量的字符串拼接在一起底層緩沖的初始容量足夠大可以包含三個待連接的字符串這樣我們可以將臨界區內的工作最小化進而重點測量同步的開銷
基准測試的結果
下圖是測試結果包括EliminateLocksUseBiasedLocking和DoEscapeAnalysis的不同組合
圖 基准測試的結果
[] [] [] []
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27670.html