鎖的進化


java線程是映射到操作系統的原生線程之上的,若是要喚醒或阻塞一個線程,都需要操作系統來幫忙完成,這就需要從用戶態轉換到內核態中,這種轉換需要耗費很多的處理時間,有可能比用戶代碼執行時間都要長。

爲了避免以上情況出現,一般能不用鎖就不用鎖,若是不得不使用的情況下,可以使用偏向鎖、自旋鎖等。

1、重量級鎖

鎖的意義是什麼呢?

當數據被多個線程共享時,爲了保證數據的正確性,避免競爭導致數據錯誤,這時就必須使用鎖,讓數據同時只能被一個線程得到並處理。

重量級鎖在JVM中又叫對象監視器(Monitor),它包含了一個競爭鎖的隊列和一個信號阻塞隊列,前者負責做互斥,後者負責線程同步。

重量級鎖中的掛起線程和恢復線程,都需要從用戶態轉換到內核態去執行,這其中需要耗費很多時間,對性能有很大的影響。

2、自旋鎖

其實鎖在大部分情況下會在短時間內執行完成,佔用鎖的時間不會太長,基於這種情況,自旋鎖出現了。

自旋鎖顧名思義,是採用讓當前線程不停地循環去獲取鎖,而不是讓線程掛起,這樣就少了喚醒線程的消耗。

在當前線程不停地獲取鎖時,會佔用CPU時間,增加CPU的消耗,在多核處理器上優勢比較明顯,若鎖保持的時間不長,線程競爭不激烈,自旋鎖是很不錯的。

相反,若鎖被長期佔用,線程數也不斷增加,就變成了“忙式等待”,這時自旋鎖的性能就會下降。

爲了控制自旋鎖循環的次數,JDK1.5默認自旋鎖循環10次若還未得到鎖就升級爲重量級鎖,在JDK1.6中更是引入了自適應自旋鎖,這種簡單來說,就是若自旋成功率高,就會讓線程經過較長時間的自旋,比如自旋100次;若自旋失敗率高,就會讓線程自旋次數很少,甚至不自旋,直接就升級爲重量級鎖了。

3、偏向鎖

不論是重量級鎖還是自旋鎖,在進入或退出時,都得通過CAS來修改對象頭的Mark Word來進行加鎖和釋放鎖。

針對對象頭,一般包含兩部分,第一部分用戶存儲對象自身的運行時數據(Mark Word),比如hashcode、GC分代年齡、鎖狀態標記等,另外一部分爲類型指針(Klass),即對象指向它的類元數據指針。若對象爲數據,則還會有一部分,用戶記錄數據長度的數據。



 

 

 

在一些情況下,總是同一個線程獲取到鎖,此時每次都得通過CAS修改對象頭的Mark Word,顯然是比較多餘的,由此就產生了偏向鎖。

在JDK1.6中引入了偏向鎖,當一個線程獲取到鎖的時候,會在對象頭中放入偏向線程的ID,當下次這個線程再獲取此鎖的時候,就會直接檢查線程ID是否一致,若是一致就說明獲取到鎖了,不用再進行CAS操作,若是不一致,則去檢測對象頭中Mark Word中的偏向鎖標記是否爲1,若是,則嘗試使用CAS修改對象頭中的偏向線程ID爲當前線程,若不是,則使用CAS競爭鎖。

偏向鎖使用了一種競爭纔會釋放鎖的機制,也就是隻有其他線程競爭偏向鎖時,持有偏向鎖的線程纔有可能釋放鎖。

偏向鎖在JDK1.6和1.7中都是默認開啓的,但是需要在程序啓動幾秒之後纔會自動開啓,若是不想延遲開啓,可以修改參數-XX:BiasedLockingStartupDelay=0

現在項目使用多線程的情況比較常見,競爭鎖也是比較激烈,每次都是從偏向鎖升級到輕量級鎖,而偏向鎖用得比較少,所以你也可以關閉偏向鎖,直接默認進入輕量鎖(自旋鎖)狀態,使用參數-XX:-UseBiasedLocking=false即可

對於鎖來說,還有一些其他優化,如鎖削除和鎖膨脹。

鎖削除就是虛擬機即時編譯器(JIT)運行時,依據逃逸分析的數據檢測到不可能存在競爭鎖,就自動把鎖削除了,比如局部變量作爲鎖等。

鎖膨脹,簡單來說就是頻繁執行中對同一對象加鎖,反覆的加鎖和解鎖勢必會帶來很大的性能消耗,這時會自動將鎖的範圍擴展到頻繁執行的外部,將反覆鎖合併爲一個鎖。

4、鎖升級

以上鎖在使用過程中,會自動進行升級,升級的順序:偏向鎖->自旋鎖→重量級鎖

對於鎖升級過程,可以參考以下流程

1、檢測Mark Word裏面是不是當前線程ID,若是,表示當前線程處於偏向鎖,並獲取到鎖

2、若不是,則嘗試使用CAS修改Mark Word置爲當前線程ID,若修改成功,則當前線程獲得偏向鎖,並置偏向位爲1

3、若修改失敗,則說明發生了競爭,撤銷偏向鎖,升級爲輕量級鎖

4、當前線程通過CAS修改Mark Word爲鎖記錄指針,如果成功,則獲取到鎖

5、若失敗,表示有其他線程在競爭鎖,當前線程便使用自旋鎖嘗試獲取鎖

6、若自旋成功,則依然處於輕量級鎖

7、若自旋失敗,則升級爲重量級鎖,線程開始阻塞

5、鎖的優缺點對比

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法比,僅存在納秒級的差距 如果線程間存在鎖金正,會額外帶來鎖撤銷的消耗 適用只有一個線程訪問同步塊的的場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度 若線程始終獲取不到鎖,自旋會白白浪費資源,消耗CPU 追求響應時間,鎖佔用時間很短的情況
重量級鎖 線程競爭不使用自旋,不消耗CPU 線程阻塞,響應時間變慢,掛起和喚醒比較消耗資源 追求吞吐量,鎖佔用時間較長
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章