Java中Synchronized的優化原理


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

原本的問題

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

因此,在鎖競爭激烈的情況下,Synchronized同步鎖在性能上就表現得非常糟糕,它也常被大家稱為重量級鎖

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

在單個線程重復申請鎖的情況下,JDK1.5 版本的 Lock 性能要比 Synchronized 鎖的性能好很多,也就是當時的 Synchronized 並不具備可重入鎖的功能。

那么當時的 Synchronized 是怎么實現的?又為什么不具備可重入的功能呢?

Synchronized原理

JVM 中的同步是基於進入和退出管程(Monitor)對象實現的。每個對象實例都會有一個 Monitor,Monitor 可以和對象一起創建、銷毀。

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

接下來當線程獲取到對象的 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 鎖。如果你有什么想法,歡迎在下方留言。

有興趣的話可以訪問我的博客或者關注我的公眾號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM