爲了解決併發操作時的數據一致性問題,java提供鎖機制,通過互斥同步或非阻塞同步來保證線程安全。但只要是同步,就會對程序的執行效率產生影響。從JDK1.6開始,HotSpot虛擬機實現了各種鎖優化技術,如適應性自旋、鎖消除、鎖粗化、輕量級鎖和偏向鎖等。這些技術使得線程之間可以更高效地共享數據,解決競爭問題,從而提高程序執行效率。
下面介紹這五種鎖優化技術原理
一、自旋鎖與自適應自旋
引出問題:
在傳統的互斥同步中,得到鎖的線程進入同步代碼,沒有得到鎖的線程則只能線程掛起,進入阻塞狀態,而掛起線程和恢復線程都需要轉到內核態由操作系統來完成,如果執行線程等待鎖的時間很短,那麼線程掛起和線程恢復就屬於性能浪費。
自旋鎖:
自旋鎖就用來解決上述問題。如果兩個線程同時競爭一個鎖,我們可以讓沒有競爭到的線程“稍等一下”,但並不放棄處理器的執行時間,也就是不掛起,看看持有鎖的線程會不會很快地釋放鎖。爲了讓線程等待,我們讓線程執行一個忙循環,也就是自旋,這項技術就是“自旋鎖”。
優缺點:
自旋鎖在鎖被佔用時間很短的情況下效果非常好,但是如果鎖被佔用的時間較長,那麼自旋的線程會佔用處理器資源,造成性能上的浪費。jdk1.6默認自旋是10次,用戶可以使用參數-XX:PreBlockSpin來更改。
自適應自旋:
自適應自旋是對自旋鎖的改進。自適應自旋以爲着自旋次數不再固定,而是根據前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。也就是通過之前線程的經驗得知獲得這個鎖的難易程度。以決定少量自旋還是多次自旋,甚至直接掛起。
二、鎖消除
引出問題:
java程序中同步代碼非常的普遍,有可能同步代碼中並沒有會產生鎖競爭的共享變量,此時同步就白白消耗了性能。
鎖消除
鎖消除是指虛擬機即時編譯器在運行時,對一些代碼要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行消除。
舉個例子:
public String concatString(String s1,String s2,String s3){
return s1+s2+s3;
}
這段代碼看起來沒有同步,實際上在jdk1.5之前,它會被轉化成StringBuffer對象的連續appen()操作,即:
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();
}
StringBuffer對象的appen()方法是同步方法,但是虛擬機觀察對象sb,發現sb的作用域在concatString()方法內,用戶調用concatString()方法永遠不會存在共享變量的競爭,所以即時編譯器將這個鎖消除掉了。
三、鎖粗化
引出問題:
通常情況下,我們加鎖的代碼以你該當越短越越好,這樣可以減少串行執行的時間,提升執行效率。但是如果一系列的連續操作都對同一個對象反覆加鎖解鎖,那麼頻繁的同步互斥操作就會影響性能。
鎖粗化:
比如上一節中的連續append()方法的情況,都對同一個對象加鎖,如果虛擬機探測到有這樣一連串的操作都對同一個對象加鎖解鎖,將會加鎖同步的範圍粗化到一連串操作的外部,講多次加鎖解鎖轉化爲一次加鎖解鎖。這就是鎖粗化。
四、輕量級鎖
輕量級鎖是使用CAS操作優化有同步但無競爭的情況。
原理:
要了解輕量級鎖的原理,先了解HotSpot虛擬機的對象頭的內存佈局。
HotSpot虛擬機的對象頭包含兩部分信息,一部分用於存儲自身運行時數據,如hash碼、GC分代年齡等,官方稱它爲“Mark Word”,它是實現輕量級鎖和偏向鎖的關鍵。另一部分用於存儲指向方法區對象類型數據的指針。
Mark Word在32位和64位虛擬機中分別佔32bit長度和64bit長度。32位虛擬機中,Mark Word用2bit空間來存儲鎖標誌位。存儲內容如下:
存儲內容 | 標誌位 | 狀態 |
---|---|---|
對象哈希碼、對象GC分代年齡 | 01 | 未鎖定 |
指向鎖記錄的指針 | 00 | 輕量級鎖定 |
指向重量級鎖的指針 | 10 | 重量級鎖定 |
空,不需要記錄信息 | 11 | GC標記 |
偏向縣城id、偏向時間戳、對象分代年齡 | 01 | 可偏向 |
在代碼進入同步塊的時候,如果對象沒有被鎖定(01),虛擬機在當前線程的棧幀中建立一個名爲鎖記錄的空間,用於存儲對象目前Mark Word的拷貝。
然後,虛擬機將使用CAS操作嘗試將對象的Mark Word更新爲指向所記錄的指針。如果成功,那麼這個線程擁有了該對象的鎖,並將該對象標誌位改爲00,表示該對象處於輕量級鎖定狀態;如果更新操作失敗了,虛擬機將檢查對象的Mark Word是否指向當前線程的棧幀,若是,則說明當前線程擁有這個對象的鎖,線程直接進入同步塊執行,若否,說明這個鎖已經被其他線程搶佔了。此時多個線程爭用同一個鎖,輕量級鎖不再有效,要膨脹爲重量級鎖,鎖標誌變爲10。
適用情況:
大多數情況下,整個同步週期內是不存在競爭的,此時輕量級鎖適用CAS操作避免了適用互斥量的開銷。但在存在競爭的情況下,輕量級鎖除了互斥量的開銷,還有CAS操作,性能會比傳統重量級鎖更差。
五、偏向鎖
偏向鎖的意思是這個鎖會偏向於第一次獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他線程獲取,則持有偏向鎖的線程永遠不需要進行同步。
原理:
當鎖對象第一次被線程獲取的時候,虛擬機會將對象頭的標誌位設爲01,即偏向模式。同時使用CAS操作把獲取到這個鎖的線程的ID記錄在對象Mark Word之中。如果CAS成功,持有偏向鎖的線程以後每次進入這個對象鎖相關的同步塊將不再同步。
當有另外一個線程去嘗試獲取這個鎖時,偏向模式宣告結束。根據目前是否處於鎖定狀態,撤銷偏向後恢復到輕量級鎖(00)或爲鎖定(01)狀態。
適用情況:
和輕量級鎖相似,偏向鎖可以提高有同步但無競爭的情況,偏向鎖比輕量級鎖優化得更徹底。如果程序中大多數的鎖總是被多個不同的線程爭搶,那麼偏向模式就是多餘的。
相關虛擬機參數
啓用偏向鎖:-XX:+UseBiasedLocking
禁用偏向鎖:-XX:-UseBiasedLocking
虛擬機實現了多種鎖優化技術,但是都不能直接解決所有的性能問題。必須針對不同場景,選擇合適的鎖優化技術,提高程序性能。