1. 程式人生 > >java併發之----簡要介紹java對鎖的幾種優化

java併發之----簡要介紹java對鎖的幾種優化

引言

新版本java對鎖的實現引進了許多技術進行優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等
鎖主要存在四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨著競爭的激烈而逐漸升級。注意鎖可以升級不可降級,這種策略是為了提高獲得鎖和釋放鎖的效率。
java中關於鎖的名詞很多,若不注意區分很容易混淆,我們可以按照類別來記憶,例如下面介紹的“偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化”是一些關於鎖的優化的技術,而“可重入鎖、公平鎖”是鎖的不同分類

(1)偏向鎖

引入偏向鎖的目的和引入輕量級鎖的目的很像,他們都是為了沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗。但是不同是:輕量級鎖在無競爭的情況下使用 CAS 操作去代替使用互斥量。而偏向鎖在無競爭的情況下會把整個同步都消除掉。

偏向鎖的“偏”就是偏心的偏,它的意思是會偏向於第一個獲得它的執行緒,如果在接下來的執行中,該鎖沒有被其他執行緒獲取,那麼持有偏向鎖的執行緒就不需要進行同步!關於偏向鎖的原理可以檢視《深入理解Java虛擬機器:JVM高階特性與最佳實踐》第二版的13章第三節鎖優化。

但是對於鎖競爭比較激烈的場合,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的執行緒都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗後,並不會立即膨脹為重量級鎖,而是先升級為輕量級鎖。

(2)輕量級鎖

倘若偏向鎖失敗,虛擬機器並不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優化手段(1.6之後加入的)。輕量級鎖不是為了代替重量級鎖,它的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗,因為使用輕量級鎖時,不需要申請互斥量。另外,輕量級鎖的加鎖和解鎖都用到了CAS操作。 關於輕量級鎖的加鎖和解鎖的原理可以檢視《深入理解Java虛擬機器:JVM高階特性與最佳實踐》第二版的13章第三節鎖優化。

輕量級鎖能夠提升程式同步效能的依據是“對於絕大部分鎖,在整個同步週期內都是不存在競爭的”,這是一個經驗資料。如果沒有競爭,輕量級鎖使用 CAS 操作避免了使用互斥操作的開銷。但如果存在鎖競爭,除了互斥量開銷外,還會額外發生CAS操作,因此在有鎖競爭的情況下,輕量級鎖比傳統的重量級鎖更慢!如果鎖競爭激烈,那麼輕量級將很快膨脹為重量級鎖!

(3)自旋鎖和自適應自旋

輕量級鎖失敗後,虛擬機器為了避免執行緒真實地在作業系統層面掛起,還會進行一項稱為自旋鎖的優化手段。

互斥同步對效能最大的影響就是阻塞的實現,因為掛起執行緒/恢復執行緒的操作都需要轉入核心態中完成(使用者態轉換到核心態會耗費時間)。

一般執行緒持有鎖的時間都不是太長,所以僅僅為了這一點時間去掛起執行緒/恢復執行緒是得不償失的。 所以,虛擬機器的開發團隊就這樣去考慮:“我們能不能讓後面來的請求獲取鎖的執行緒等待一會而不被掛起呢?看看持有鎖的執行緒是否很快就會釋放鎖”。為了讓一個執行緒等待,我們只需要讓執行緒執行一個忙迴圈(自旋),這項技術就叫做自旋。

自旋鎖在 JDK1.6 之前其實就已經引入了,不過是預設關閉的,需要通過–XX:+UseSpinning引數來開啟。JDK1.6及1.6之後,就改為預設開啟的了。需要注意的是:自旋等待不能完全替代阻塞,因為它還是要佔用處理器時間。如果鎖被佔用的時間短,那麼效果當然就很好了!反之,相反!自旋等待的時間必須要有限度。如果自旋超過了限定次數任然沒有獲得鎖,就應該掛起執行緒。自旋次數的預設值是10次,使用者可以修改–XX:PreBlockSpin來更改。

另外,在 JDK1.6 中引入了自適應的自旋鎖。自適應的自旋鎖帶來的改進就是:自旋的時間不在固定了,而是和前一次同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定,虛擬機器變得越來越“聰明”了。

(4)鎖消除

鎖消除理解起來很簡單,它指的就是虛擬機器即使編譯器在執行時,如果檢測到那些共享資料不可能存在競爭,那麼就執行鎖消除。鎖消除可以節省毫無意義的請求鎖的時間。

(5)鎖粗化

通常情況下,為了保證多執行緒間的有效併發,會要求每個執行緒持有鎖的時間儘可能短,但是大某些情況下,一個程式對同一個鎖不間斷、高頻地請求、同步與釋放,會消耗掉一定的系統資源,因為鎖的請求、同步與釋放本身會帶來效能損耗,這樣高頻的鎖請求就反而不利於系統性能的優化了,雖然單次同步操作的時間可能很短。鎖粗化就是告訴我們任何事情都有個度,有些情況下我們反而希望把很多次鎖的請求合併成一個請求,以降低短時間內大量鎖請求、同步、釋放帶來的效能損耗

可重入鎖

“可重入鎖”概念是:自己可以再次獲取自己的內部鎖。比如一個執行緒獲得了某個物件的鎖,此時這個物件鎖還沒有釋放,當其再次想要獲取這個物件的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。同一個執行緒每次獲取鎖,鎖的計數器都自增1,所以要等到鎖的計數器下降為0時才能釋放鎖。

公平鎖

公平鎖是指多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。所謂的公平鎖就是先等待的執行緒先獲得鎖。