Java中Synchronized的優化原理

咱們知道,從 JDK1.6 開始,Java 對 Synchronized 同步鎖作了充分的優化,甚至在某些場景下,它的性能已經超越了 Lock 同步鎖。那麼就讓咱們來看看,它到底是如何優化的。 git

本來的問題

Synchronized是基於底層操做系統的 Mutex Lock 實現的,每次獲取鎖和釋放鎖的操做都會帶來用戶態內核態的切換,從而增長系統性能開銷。github

所以,在鎖競爭激烈的狀況下,Synchronized同步鎖在性能上就表現得很是糟糕,它也常被你們稱爲重量級鎖安全

到了 JDK1.5 版本,併發包中新增了 Lock 接口來實現鎖功能,它提供了與 Synchronized 關鍵字相似的同步功能,只是在使用時須要顯示獲取鎖和釋放鎖。多線程

在單個線程重複申請鎖的狀況下,JDK1.5 版本的 Lock 性能要比 Synchronized 鎖的性能好不少,也就是當時的 Synchronized 並不具有可重入鎖的功能。併發

那麼當時的 Synchronized 是怎麼實現的?又爲何不具有可重入的功能呢?性能

Synchronized原理

JVM 中的同步是基於進入和退出管程(Monitor)對象實現的。每一個對象實例都會有一個 Monitor,Monitor 能夠和對象一塊兒建立、銷燬。優化

當多個線程同時訪問一段同步代碼時,多個線程會先被存放在EntryList集合(也可稱爲阻塞隊列)中,處於BLOCKED狀態的線程,都會被加入到該列表。spa

接下來當線程獲取到對象的 Monitor 時,Monitor 是依靠底層操做系統的 Mutex Lock 來實現互斥的,線程申請 Mutex 成功,則持有該 Mutex,其它線程將沒法獲取到該 Mutex。操作系統

若是線程調用 wait() 方法,就會釋放當前持有的 Mutex,而且該線程會進入WaitSet集合(也可稱爲等待隊列)中,等待下一次被喚醒。此時線程會處於WAITING或者TIMEDWAITING狀態,線程

若是當前線程順利執行完方法,也將釋放 Mutex。

總的來講,就是同步鎖在這種實現方式中,因 Monitor 是依賴於底層的操做系統實現,存在用戶態內核態之間的切換(能夠理解爲上下文切換),因此增長了性能開銷。

鎖升級

爲了提高性能,JDK1.6 引入了偏向鎖、輕量級鎖、重量級鎖概念,來減小鎖競爭帶來的上下文切換,而正是新增的Java對象頭實現了鎖升級功能。

所謂鎖升級,就是指

Synchronized 同步鎖初始爲偏向鎖,隨着線程競爭愈來愈激烈,偏向鎖升級到輕量級鎖,最終升級到重量級鎖

偏向鎖

偏向鎖主要用來優化同一線程屢次申請同一個鎖的競爭,也就是如今的Synchronized鎖實際已經擁有了可重入鎖的功能。

爲何要有偏向鎖?由於在咱們的應用中,可能大部分時間是同一個線程競爭鎖資源(好比單線程操做一個線程安全的容器),若是這個線程每次都要獲取鎖和釋放鎖,那麼就在不斷的從內核態用戶態之間切換。

那麼有了偏向鎖,當一個線程再次訪問這個同步代碼或方法時,該線程只需去對象頭中去判斷一下是否當前線程是否持有該偏向鎖就能夠了。

一旦出現其它線程競爭鎖資源時,偏向鎖就會被撤銷。偏向鎖的撤銷須要等待全局安全點(JVM的stop the world),暫停持有該鎖的線程,同時檢查該線程是否還在執行該方法,若是是,則升級鎖,反之則被其它線程搶佔。

輕量級鎖

當有另一個線程競爭獲取這個鎖時,因爲該鎖已是偏向鎖,當發現對象頭中的線程 ID 不是本身的線程 ID,就會進行 CAS 操做獲取鎖,若是獲取成功,直接替換對象頭中的線程 ID 爲本身的 ID,該鎖會保持偏向鎖狀態;若是獲取鎖失敗,表明當前鎖有必定的競爭,偏向鎖將升級爲輕量級鎖

輕量級鎖適用於線程交替執行同步塊的場景,絕大部分的鎖在整個同步週期內都不存在長時間的競爭。

輕量級鎖也支持自旋,所以其餘線程再次爭搶時,若是CAS失敗,將再也不會進入阻塞狀態,而是不斷自旋。

之因此自旋更好,是由於以前說了,默認線程持有鎖的時間都不會太長,若是線程被掛起阻塞可能代價會更高。

若是自旋鎖重試以後搶鎖依然失敗,那麼同步鎖就會升級至重量級鎖

重量級鎖

在這個狀態下,未搶到鎖的線程都會進入 Monitor,以後會被阻塞在WaitSet集合中,也就變成了優化以前的Synchronized鎖

JVM參數優化

偏向鎖升級爲輕量級鎖時,會發生stop the world,若是系統經常是多線程競爭,那麼禁止偏向鎖也許是更好的選擇,能夠經過如下JVM參數進行優化:

// 關閉偏向鎖(默認打開)
-XX:-UseBiasedLocking
// 設置重量級鎖
-XX:+UseHeavyMonitors複製代碼

輕量級鎖擁有自旋鎖的功能,那麼若是線程持有鎖的時間很長,那麼競爭的線程也會經常處於自旋狀態,佔用系統 CPU ,增長系統開銷,那麼此時關閉自旋鎖的優化能夠更好一些:

-XX:-UseSpinning複製代碼

總結

以上即是 Java 中針對 Synchronized 鎖的優化,也正是由於這個優化,ConcurrentHashMap 在 JDK1.8 以後,再次採用 Synchronized 鎖。若是你有什麼想法,歡迎在下方留言。

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

death00.github.io/

相關文章
相關標籤/搜索