1. 偏向鎖
偏向鎖就是在運行過程中,對象的鎖偏向某個線程。即在開啓偏向鎖機制的情況下,某個線程獲得鎖,當該線程下次再想要獲得鎖時,不需要重新申請獲得鎖(即忽略synchronized關鍵詞),直接就可以執行同步代碼,比較適合競爭較少的情況。
偏向鎖的目標是,減少無競爭且只有一個線程使用鎖的情況下,使用輕量級鎖而產生的性能消耗。輕量級鎖每次申請、釋放鎖都至少需要一次CAS
,但偏向鎖只有初始化時需要一次CAS。
如果明顯存在其他線程申請鎖,那麼偏向鎖將很快膨脹爲輕量級鎖。如果需要,使用參數
-XX:-UseBiasedLocking
禁止偏向鎖優化(默認打開)。
1.1 偏向鎖獲取過程
- 查看
Mark Word
中偏向鎖的標識以及鎖標誌位,若是否爲偏向鎖爲1,並且鎖標誌位爲01,則該鎖爲可偏向狀態。 - 若該鎖爲可偏向狀態,判斷
Mark Word
中的線程ID與當前線程ID是否相等,如果相同,則直接執行同步代碼,否則通過CAS
操作競爭鎖。 - 如果競爭成功,將
Mark Word
中線程ID設置爲當前線程ID,然後執行同步代碼。 - 如果競爭失敗,說明有其他線程競爭。持有偏向鎖狀態的線程在沒有字節碼正在執行的情況下釋放鎖,然後恢復到未鎖定狀態或者膨脹爲輕量級鎖。
1.2 偏向鎖釋放過程
持有偏向鎖的線程不會主動釋放鎖,只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖狀態的線程纔會釋放鎖。持有持有偏向鎖的線程需要等到所有的同步任務執行完成之後(即沒有字節碼正在執行),纔會暫停持有偏向鎖的線程,然後恢復到未鎖定狀態或者膨脹爲輕量級鎖。
Mark Word是對象頭的一部分,每個線程都擁有自己的線程棧(虛擬機棧),記錄線程和函數調用的基本信息。
2. 輕量級鎖
輕量級鎖是相對於重量級鎖而言的,使用時不需要申請互斥量。而是在沒有多線程競爭的情況下,使用輕量級鎖能夠減少性能消耗,但是當多個線程同時競爭鎖時,輕量級鎖會膨脹爲重量級鎖。
輕量級鎖的目標是,減少無實際競爭情況下,使用重量級鎖產生的性能消耗,包括系統調用引起的內核態與用戶態切換、線程阻塞造成的線程切換等。
1.1 輕量級鎖獲取過程
- 當線程執行代碼進入同步塊時,若
Mark Word
鎖標識爲無鎖狀態(是否爲偏向鎖爲0,鎖標誌位爲01),虛擬機會在當前線程的棧幀中建立一個名爲鎖記錄(Lock Record
)的空間(用於存儲當前對象的Mark Word
的拷貝,官方稱之爲Dispalced Mark Word
)。 - 複製對象頭中的
Mark Word
到鎖記錄中。 - 複製成功後,虛擬機將使用
CAS
操作嘗試將對象的Mark Word
更新爲指向Lock Record
的指針,並將Lock Record
裏的owner
指針指向對象的Mark Word
- 如果更新成功,則這個線程擁有了這個鎖,並將鎖標誌位設置00,此對象處於輕量級鎖定狀態。
- 如果更新失敗,虛擬機會檢查對象的Mark Word是否指向當前線程的棧幀。如果是,則說明當前線程已經擁有這個鎖,可進入執行同步代碼;如果不是,則說明多個線程競爭,輕量級鎖就會膨脹爲重量級鎖,
Mark Word
中存儲重量級鎖(互斥鎖)的指針,後面等待鎖的線程也要進入阻塞狀態。
1.2 輕量級鎖釋放過程
- 通過CAS操作嘗試把線程中複製的
Displaced Mark Word
對象替換當前的Mark Word。 - 如果替換成功,整個同步過程就完成了。
- 如果替換失敗,說明有其他線程嘗試過獲取該鎖(此時鎖已膨脹),那就要在釋放鎖的同時,喚醒被掛起的線程。
3. 重量級鎖
重量級鎖爲synchronized,通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的操作系統的Mutex Lock
來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是爲什麼synchronized效率低的原因。因此,這種依賴於操作系統Mutex Lock
所實現的鎖我們稱之爲“重量級鎖”。
4. 自旋鎖
在自旋狀態下,當一個線程A嘗試進入同步代碼塊,但是當前的鎖已經被線程B佔有時,線程A不進入阻塞狀態,而是不停的空轉,等待線程B釋放鎖。如果鎖的線程能在很短時間內釋放資源,那麼等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞狀態,只需自旋,等持有鎖的線程釋放後即可立即獲取鎖,避免了用戶線程和內核的切換消耗。
優點:開啓自旋鎖後能減少線程的阻塞,在對於鎖的競爭不激烈且佔用鎖時間很短的代碼塊來說,能提升很大的性能,在這種情況下自旋的消耗小於線程阻塞掛起的消耗。
缺點:在線程競爭鎖激烈,或持有鎖的線程需要長時間執行同步代碼塊的情況下,使用自旋會使得CPU做太多無用功。
JDK1.6中,設置參數
-XX:+UseSpinning
開啓。JDK1.7後,由JVM自動控制。
5. 自適應自旋鎖
自適應意味着自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定:
- 如果在同一個鎖對象上,自旋等待之前成功獲得過的鎖,並且持有鎖的線程正在運行中,那麼虛擬機就會認爲這次自旋也很有可能再次成功,因此允許自旋等待持續相對更長的時間。
- 相反的,如果對於某個鎖,自旋很少成功獲得過,那麼以後要獲取這個鎖時將可能減少自旋時間甚至省略自旋過程,以避免浪費處理器資源。
自適應自旋解決的是“鎖競爭時間不確定”的問題。JVM很難感知確切的鎖競爭時間,而交給用戶分析就違反了JVM的設計初衷。自適應自旋假定不同線程持有同一個鎖對象的時間基本相當,競爭程度趨於穩定。因此,可以根據上一次自旋的時間與結果調整下一次自旋的時間。
6. 總結
鎖類型 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 | 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 | 適用於只有一個線程訪問同步塊場景。 |
輕量級鎖 | 競爭的線程不會阻塞,提高了程序的響應速度。 | 如果始終得不到鎖競爭的線程使用自旋會消耗CPU。 | 追求響應時間。同步塊執行速度非常快。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU。 | 線程阻塞,響應時間緩慢。 | 追求吞吐量。同步塊執行速度較長。 |
參考鏈接:
https://www.jianshu.com/p/8c255b942535
Kotlin開發者社區
專注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函數式編程、編程思想、"高可用,高性能,高實時"大型分佈式系統架構設計主題。