天天看點

HotSpot虛拟機各種鎖優化技術

高效并發是從JDK1.5到JDK1.6的一個重要改進,HotSpot虛拟機開發團隊在這個版本上花費了大量的精力去實作各種鎖優化技術,如自旋,适應性自旋,鎖消除、鎖粗化、輕量級鎖和偏向鎖等,這些技術都是為了線上程之間更高效地共享資料,以及解決競争問題,進而提高程式的執行效率。

自旋鎖和自适應自旋

如果實體機器有一個以上的處理器,能讓兩個或以上的線程同時并行執行,我們就可以讓後面請求鎖的那個線程“稍等一下”,但并放棄處理器的執行時間,看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們隻需讓線程執行一個忙循環(自旋),這項技術就是所謂的自旋鎖。

自旋鎖在JDK 1.4.2 中就已經引入,隻不過預設是關閉的,可以使用-XX:UseSpinning參數來開啟,在JDK 1.6中就已經改為預設開啟了。自旋等待不能代替阻塞,且先不說對處理器數量的要求,自旋等待本身雖然避免了線程切換的開銷,但它是要占用處理器時間的,是以,如果鎖被占用的時間很短,自旋等待的效果就會非常好,反之,如果鎖被占用的時間很長,那麼自旋的線程隻會白白消耗處理器資源,而不會做任何有用的工作,反而會帶來性能上的浪費。是以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式挂起線程了。自旋次數的預設值是10次。

在JDK 1.6 中引入了自适應自旋鎖。自适應意味着自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀态來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運作中,那麼虛拟機就會認為這次自旋也很有可能再次成功,進而它将允許自旋等待持續相對更長的時間,比如100個循環。另外,如果對于某個鎖,自旋很少成功獲得過,那在以後要擷取這個鎖時将可能省略點自旋過程,以避免浪費處理器資源。

鎖消除

鎖消除是指虛拟機即時編譯器在運作時,對一些代碼上要求同步,但是被檢測到不可能存在共享資料競争的鎖進行消除。鎖消除的主要判定依據來源于逃逸分析的資料支援,如果判斷在一段代碼中,堆上的所有資料都不會逃逸出去進而被其他線程通路到,那就可以把它們當做棧上資料對待,認為它們是線程私有的,同步加鎖自然就無須進行。

鎖粗化

原則上,我們在編寫代碼的時候,總是推薦将同步塊的作用範圍限制得盡量小—隻在共享資料的實際作用域中才進行同步,這樣是為了使得需要同步的操作數量盡可能變小,如果存在競争,那等待鎖的線程也能盡快拿到所。大部分情況下,上面的原則都是正确的,但是如果一系列的連續操作都對同一個對象反複加鎖和解鎖,甚至加鎖操作都是出現在循環體中,那即使沒有線程競争,頻繁地進行互斥同步操作也會導緻不必要的性能損耗。

輕量級鎖

輕量級鎖是JDK 1.6 之中加入的新型鎖機制,它名字中的“輕量級”是相對于使用作業系統互斥量來實作的傳統鎖而言的,是以傳統的鎖機制就稱為“重量級”鎖。首先需要強調一點的是,輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多線程競争的前提下,減少傳統的重量級鎖使用作業系統互斥量産生的性能消耗。

在代碼進入同步塊的時候,如果此同步對象沒有被鎖定,虛拟機首先将在目前線程的棧針中建立一個名為鎖記錄的控件,用于存儲對象目前的Mark Word的拷貝。然後,虛拟機将使用CAS操作嘗試将對象的Mark Word更新為指向Lock Record的指針。如果這個更新成功了,那麼這個線程就用了該對象的鎖。

注意:要了解輕量級鎖,必須要了解HotSpot虛拟機對象的記憶體布局(将在下一篇文章介紹)

輕量級鎖能提升程式同步性能的一依據是“對于絕大部分的鎖,在整個同步周期内都是不存在競争的”,這是一個經驗資料。如果沒有競争,輕量級鎖使用CAS操作避免了使用互斥量的開銷,但如果存在鎖競争,除了互斥量的開銷外,還額外發生了CAS操作,是以在有競争的情況下,輕量級鎖會比傳統的重量級鎖更慢。

偏向鎖

偏向鎖也是在JDK 1.6 中引入的一向鎖優化,它的目的是消除資料在無競争情況下的同步原語,進一步提高程式的運作性能。它的意思是這個鎖會偏向于第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他的線程擷取,則持有偏向鎖的線程将永遠不需要再進行同步。

繼續閱讀