自旋在synchronized底層實現性能提升

JVM的同步是基於進入與退出監視器對象(管程對象)(Monitor)來實現的,每個對象實例都會有一個Monitor對象,Monitor對象會和Java對象一同創建和銷燬。Monitor對象是由C++來實現的。

當多個線程同時訪問一段同步代碼時,這些線程會被放到一個EntryList集合中,處於阻塞狀態的線程都會被放到該列表中。接下來當線程獲取到對象的Monitor時,Monitor依賴於底層操作系統的mutex lock來實現互斥的,線程獲取mutex成功,則會持有該mutex,這時其他線程就無法再獲取到該mutex。

如果線程調用了wait方法,那麼該線程就會釋放掉所持有的mutex,並且該線程會進入到WaitSet集合(等待集合)中,等待下一次被其他線程調用notify/notifyAll方法來喚醒。如果當前線程順利執行完方法,那麼它也會釋放掉所持有的鎖。

總結一下:同步鎖在這種實現方式當中,由於Monitor是依賴於底層操作系統實現的,這樣就會存在用戶態和內核態之間的切換,所以會增加性能開銷。

通過對象互斥鎖的概念來保證共享數據操作的完整性。每個對象都對應於一個可稱爲互斥鎖的標記,這個標記用於保證在任何時刻,只能有一個線程訪問對象。

那些處於EntryList與WaitSet中的線程均處於阻塞狀態,阻塞操作是由操作系統來完成的。在Linux下是通過pthread_mutex_lock函數實現的。線程被阻塞後便會進入到內核調度狀態,這會導致系統在用戶態和內核態之間來回切換,嚴重影響鎖的性能。

解決上述問題的方式便是自旋,其原理就是當發生對Monitor的爭用時,若Owner(獲得鎖的線程)能夠在很短的時間內釋放掉鎖,則那些正在競爭的線程就可以稍微等待一下(自旋),在Owner線程釋放鎖之後,爭用線程可能會立刻獲取到鎖,從而避免了系統阻塞。不過,當Owner運行的時間超過了臨界值後,爭用線程自旋一段時間後依然無法獲取到鎖,這時爭用線程則會停止自旋而進入到阻塞狀態。

所以,總體思路就是先自旋,不成功再進行阻塞,儘量降低阻塞的可能性,這對那些執行時間很短的代碼塊來說有極大的性能提升。顯然,自旋在多處理器(多核心)上纔有意義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章