synchronized的底層實現

一、概念

(一)作用

  • 確保線程互斥的訪問同步代碼
  • 保證共享變量的修改能夠及時可見
  • 有效解決重排序問題

(二)使用場景

  • 修飾代碼塊
  • 修飾方法(普通方法和靜態方法)

(三)可重入鎖和不可重入鎖

1.不可重入鎖

當前線程執行某個方法已經獲取了該鎖,那麼在方法中嘗試再次獲取鎖時,就會獲取不到被阻塞。

不可重入鎖實現:
在這裏插入圖片描述
2.可重入鎖

如果某個線程試圖獲取一個已經由他自己持有的鎖,這個請求可以成功,那麼此時的鎖就是可重入鎖

可重入鎖實現:
在這裏插入圖片描述

爲每個鎖關聯一個持有者持有計數值,當計數值爲 0 時,這個鎖會被認爲沒有被任何線程持有,當現場請求一個未被持有的鎖時,jvm 會把這個鎖給這個線程,並記下這個鎖的持有者,同時計數值置爲 1,如果同一個線程再次獲取這個鎖,計數值將遞增,當線程退出同步代碼塊時,計數值將遞減,當計數值爲 0 時,鎖將被釋放。


二、實現原理

synchronized 是由一對 monitorenter/monitorexit 指令實現的,monitor 對象是同步的基本實現單元。在 JVM 處理字節碼會出現相關指令。

jvm 基於進入和退出 Monitor 對象來實現方法同步和代碼塊同步。

代碼塊的同步是利用 monitorenter 和 monitorexit 這兩個字節碼指令。它們分別位於同步代碼塊的開始和結束位置。當 jvm 執行到 monitorenter 指令時,當前線程試圖獲取 monitor 對象的所有權,如果未加鎖或者已經被當前線程所持有,就把鎖的計數器 + 1;當執行 monitorexit 指令時,鎖計數器 - 1;當鎖計數器爲 0 時,該鎖就被釋放了。如果獲取 monitor 對象失敗,該線程則會進入阻塞狀態,直到其他線程釋放鎖。

方法級的同步是隱式,即無需通過字節碼指令來控制的,它實現在方法調用和返回操作之中。JVM 可以從方法常量池中的方法表結構 (method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標誌區分一個方法是否同步方法。當方法調用時,調用指令將會 檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先持有 monitor(虛擬機規範中用的是管程一詞), 然後再執行方法,最後再方法完成 (無論是正常完成還是非正常完成) 時釋放 monitor。


三、鎖升級原理(鎖膨脹)

Synchronized 是通過對象內部的一個叫做監視器鎖(monitor)來實現的,監視器鎖本質又是依賴於底層的操作系統的 Mutex Lock(互斥鎖)來實現的。而操作系統實現線程之間的切換需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是爲什麼 Synchronized 效率低的原因。因此,這種依賴於操作系統 Mutex Lock 所實現的鎖我們稱之爲 “重量級鎖”。

Java SE 1.6 爲了減少獲得鎖和釋放鎖帶來的性能消耗,引入了 “偏向鎖” 和 “輕量級鎖”:鎖一共有 4 種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。鎖可以升級但不能降級。

  • 無鎖:沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功,其他修改失敗的線程會不斷重試直到修改成功。

  • 偏向鎖:對象的代碼一直被同一線程執行,不存在多個線程競爭,該線程在後續的執行中自動獲取鎖,降低獲取鎖帶來的性能開銷。

  • 輕量級鎖:輕量級鎖是指當鎖是偏向鎖的時候,被第二個線程 B 所訪問,此時偏向鎖就會升級爲輕量級鎖,線程 B 會通過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。

  • 重量級鎖:又有第三個線程來訪時,輕量級鎖也會升級爲重量級鎖。當有一個線程獲取鎖之後,其餘所有等待獲取該鎖的線程都會處於阻塞狀態。


【Java 面試那點事】

這裏致力於分享 Java 面試路上的各種知識,無論是技術還是經驗,你需要的這裏都有!

這裏可以讓你【快速瞭解 Java 相關知識】,並且【短時間在面試方面有跨越式提升

面試路上,你不孤單!
在這裏插入圖片描述

發佈了165 篇原創文章 · 獲贊 852 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章