1 鎖優化歷史 synchronized 從 JDK1.0到JDK1.5 ,效率低 JDK1.5到JDK1.6,JVM團隊對synchronized進行深度優化,加入了:適應性自旋、鎖消除、鎖膨脹、輕量級鎖、偏向鎖 等優化技術 JDK1.5 開始,加入java.util.concurrent,提供A ...
目錄
1 鎖優化歷史
- synchronized 從 JDK1.0到JDK1.5 ,效率低
- JDK1.5到JDK1.6,JVM團隊對synchronized進行深度優化,加入了:適應性自旋、鎖消除、鎖膨脹、輕量級鎖、偏向鎖 等優化技術
- JDK1.5 開始,加入java.util.concurrent,提供API層面的輕量級鎖應用
為什麼優化synchronized?
互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢複線程的操作都需要轉入內核態中完成,這些操作給Java虛擬機的併發性能帶來了很大的壓力。
2 自旋鎖與自適應自旋
2.1 關於自旋鎖
自旋鎖歷史進程:首次出現在JDK1.4.2,但預設關閉(使用-XX:+UseSpinning參數開啟),在JDK6開始預設開啟。
自旋鎖實現邏輯:如果鎖被其它線程占有,那麼本線程不放棄處理器的執行時間,並執行一個忙迴圈(自旋),直至得到鎖。
自旋鎖弊端:如果鎖被占用的時間很長,那麼自旋的線程只會白白消耗處理器資源,帶來性能的浪費
自旋鎖優化:
- 引入自旋次數:如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起線程。自旋次數的預設值是10次,可以啟用參數-XX:PreBlockSpin來更改。(次數在JDK1.4.2已經實現)
- 引入了自適應自旋
2.1 自旋鎖優化:自適應自旋
原來:所有線程的自旋時間統一的(PreBlockSpin配置值 * 單次自旋時間)
自適應自旋:自旋的時間不固定,JVM根據前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
自適應自旋情形:
例如1:線程A、B,鎖L。A先通過自旋獲取鎖,那麼JVM認為B也能通過自旋獲取到鎖,隨即給B分配自旋時間,並且這個時間也是綜合之前的自旋時間
例如2:鎖L,線程A B C 通過自旋,都沒有獲取到鎖,那麼JVM會對後續的線程省略自旋過程,以免浪費處理器資源
3 鎖消除
定義:指虛擬機即時編譯器在運行時,對一些代碼要求同步,但是對被檢測到不可能存在共用數據競爭的鎖進行消除。
判斷依據:源於逃逸分析的數據支持,如果判斷到一段代碼中,在堆上的所有數據都不會逃逸出去被其他線程訪問到,那就可以把它們當作棧上數據對待,認為它們是線程私有的,同步加鎖自然就無須再進行。
舉例:
一段看起來沒有同步的代碼:
public String concatString(String s1, String s2, String s3)
{
return s1 + s2 + s3;
}
javac編譯後,會變為以下同等代碼:
public String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
//append()方法有synchronize修飾
分析:
jvm觀察變數sb,經過逃逸分析後會發現它的動態作用域被限制在concatString()方法內部。可以採用無鎖操作。
在解釋執行時這裡仍然會加鎖,但在即時編譯之後,這段代碼就會忽略所有的同步措施而直接執行。
鎖消除總結:
JVM會進行逃逸分析,符合條件的,在編譯為機器碼時,會消除所有同步措施
4 鎖粗化
定義:假如一串零碎的操作都對同一個對象加鎖,JVM會把加鎖同步的範圍擴展(粗化)到整個操作序列的外部
舉例:
例如上面concatString()方法,將鎖擴展到第一個append()操作之前最後一個append()操作之後,這樣只需加鎖一次
5 輕量級鎖
邏輯:無競爭的情況下使用CAS操作去消除同步使用的互斥量
輕量級鎖提升性能的依據:“對於絕大部分的鎖,在整個同步周期內都是不存在競爭的”這一經驗法則
過程分析:後續補充
6 偏向鎖
邏輯:在無競爭的情況下把整個同步都消除掉,連CAS操作都不去做
“偏”的理解:鎖會偏向於第一個獲得它的線程,如果在接下來的執行過程中,該鎖一直沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。
過程分析:後續補充