總結
無鎖 -> 偏向鎖 -> 輕量級鎖 (自旋鎖) -> 重量級鎖 (悲觀鎖)
鎖狀態對比:
|
偏向鎖 |
輕量級鎖 |
重量級鎖 |
適用場景 |
只有一個線程進入同步塊 |
雖然很多線程,但是沒有沖突:多條線程進入同步塊,但是線程進入時間錯開因而並未爭搶鎖 |
發生了鎖爭搶的情況:多條線程進入同步塊並爭用鎖 |
本質 |
取消同步操作 |
CAS操作代替互斥同步 |
互斥同步 |
優點 |
不阻塞,執行效率高(只有第一次獲取偏向鎖時需要CAS操作,后面只是比對ThreadId) |
不會阻塞 |
不會空耗CPU |
缺點 |
適用場景太局限。若競爭產生,會有額外的偏向鎖撤銷的消耗 |
長時間獲取不到鎖空耗CPU |
阻塞,上下文切換,用戶態切換到內核態重量級操作,消耗操作系統資源 |
1.無鎖
沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功,其他修改失敗的線程會不斷重試直到修改成功。
2.偏向鎖
在鎖對象的對象頭里面有一個 threadid 字段,在第一次訪問的時候 threadid 為空,jvm 讓其持有偏向鎖,並將 threadid 設置為其線程 id(線程第一次進入,使用CAS來設置線程id),再次進入的時候會先判斷 threadid 是否與其線程 id 一致(無需CAS了):
- 如果一致則可以直接使用此對象,降低獲取鎖帶來的性能開銷。
- 如果不一致,則升級偏向鎖為輕量級鎖
偏向鎖,指的就是偏向第一個加鎖線程a,該線程是不會主動釋放偏向鎖的,只有當其他線程b嘗試競爭偏向鎖才會被釋放。
關閉偏向鎖,通過jvm的參數-XX:UseBiasedLocking=false,則默認會進入輕量級鎖。
偏向鎖的釋放
其實就是線程b要操作的時候,看是否可以釋放掉a線程的偏向鎖。需要等待全局安全點(在這個時間點上沒有正在執行的字節碼),它會首先暫停擁有偏向鎖的線程a(達到安全點再暫停阿~),然后檢查持有偏向鎖的線程a是否還活着:
- 如果線程a不處於活動狀態,則將鎖對象的MarkWord設置成無鎖狀態,(再指向b線程)。
- 如果線程a仍然活着,擁有偏向鎖a的棧會被執行。
- 當線程a不需要用到該偏向鎖了,則恢復到無鎖,(再指向b線程)。
- 如果a還要用,則和b產生競爭,標記對象不適合作為偏向鎖。最后喚醒暫停的線程。
偏向鎖->輕量級鎖 條件
當鎖是偏向鎖的時候,被第二個線程 b 所訪問,此時偏向鎖就會升級為輕量級鎖
3.輕量級鎖
輕量級鎖是指: 當鎖是偏向鎖的時候(鎖的threadid 是 a 時),被第二個線程 b 所訪問,此時偏向鎖就會升級為輕量級鎖,線程 B 會通過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。
輕量級鎖->重量級鎖 兩個條件
- 當前只有一個等待線程b,則該線程將通過自旋進行等待。但是當自旋超過一定的次數時,輕量級鎖便會升級為重量級鎖;
- 當一個線程已持有鎖,另一個線程在自旋,而此時又有第三個線程來訪時,輕量級鎖也會升級為重量級鎖。
4.重量級鎖
指當有一個線程獲取鎖之后,其余所有等待獲取該鎖的線程都會處於阻塞狀態。
重量級鎖通過對象內部的監視器(monitor)實現,而其中 monitor 的本質是依賴於底層操作系統的 Mutex Lock 實現,操作系統實現線程之間的切換需要從用戶態切換到內核態,切換成本非常高。
鎖分級別原因
- 鎖升級是為了減低了synchronized(初始設計就是重量級鎖)帶來的性能消耗。沒有優化以前,synchronized是重量級鎖-悲觀鎖 (對比:樂觀鎖vs悲觀鎖),使用 wait 和 notify、notifyAll 來切換線程狀態非常消耗系統資源。
- synchronized鎖在線程運行到該代碼塊的時候,讓程序的運行級別從用戶態切換到內核態,把所有的線程掛起,讓cpu通過操作系統指令,去調度多線程之間,誰執行代碼塊,誰進入阻塞狀態。
- 這樣會頻繁出現程序運行狀態的切換,線程的掛起和喚醒,這樣就會大量消耗資源,程序運行的效率低下。為了提高效率,jvm的開發人員,把鎖分為 無鎖、偏向鎖、輕量級鎖、重量級鎖 狀態,盡量讓多線程訪問公共資源的時候,不進行程序運行狀態的切換(用戶態切換到內核態)。
用戶態和內核態
進程在運行時一般都是用戶態,此時權限是最低的。
但一旦要執行系統硬件層的操作,比如讀寫文件,網絡傳輸等需要從用戶態切換到內核態才能執行,但這個切換過程是很耗時的。
部分內容摘自: