摘要:由此可見,自旋鎖和各有優劣,他們分別適用于競爭不多和競爭激烈的場景中。每一個試圖進入同步代碼塊的線程都會被封裝成對象,它們或在對象的中,或在中,等待成為對象的成為的對象即獲取了監視器鎖。
前言
系列文章目錄
前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機制,大致知道了這些關鍵字和方法是干什么的,以及怎么用。
但是,知其然,并不知其所以然。
例如:
什么是監視器鎖?
JAVA中任何對象都可以作為鎖,那么鎖信息是怎么被記錄和存儲的?
監視器鎖是怎樣被獲取的?
監視器鎖是怎樣被釋放的?
什么是wait set?
本篇我們將來解答這些問題。
spin-lock 和 suspend-lock總的來說,鎖有兩種不同的實現方式,一種是自旋,一種是掛起。
(suspend-lock不知道怎么翻譯,感覺叫"掛起鎖"或"懸掛鎖"都太難聽了,后面就直接不翻譯了)
自旋鎖是一種樂觀鎖,它樂觀地認為鎖資源沒有被占用,或者即使被占用了,也很快就會被釋放, 所以當它發現鎖已經被占用后,大多會在原地忙等待(一般是在死循環中等待,這也就是自旋的由來), 直到鎖被釋放,我們在之前分析AQS的文章中提過,AQS處在阻塞隊列頭部的線程用的就是自旋的方式來等待鎖。
suspend-lock是一種悲觀鎖,它悲觀地認為鎖競爭總是經常發生的,如果鎖被占用了,基本短時間內不會釋放,所以他會讓出CPU資源,直接掛起,等待條件滿足后,別人將自己喚醒。
自旋鎖的優點是實現簡單,只需要很小的內存,在競爭不多的場景中性能很好。但是如果鎖競爭很多,那么大量的時間會浪費在無意義的自旋等待上,造成CPU利用率降低。
suspend-lock的優點是CPU利用率高,因為在發現鎖被占用后,它會立即釋放自己剩下的CPU時間隙(time-slice)給其他線程,以期望獲得更高的CPU利用率。但是因為線程的掛起與喚醒需要通過操作系統調用來完成,這涉及到用戶空間和內核空間的轉換,線程上下文的切換,所以即使在競爭很少的場景中,這種鎖也會顯得很慢。但是如果鎖競爭很激烈,則這種鎖就可以獲得很好的性能。
由此可見,自旋鎖和suspend-lock各有優劣,他們分別適用于競爭不多和競爭激烈的場景中。
在實際的應用中,我們可以綜合這兩種方式的優點,例如AQS中,排在阻塞隊列第一位的使用自旋等待,而排在后面的線程則掛起。
而我們今天要講的synchronized,使用的是suspend-lock方式。
synchronized的實現既然前面提到了synchronized用的是suspend-lock方式,在看synchronized的實現原理之前,我們不妨來思考一下: 如果要我們自己設計,該怎么做?
前幾篇我們提到過:
每個java對象都可以用做一個實現同步的鎖, 這些鎖被稱為內置鎖(Intrinsic Lock)或者監視器鎖(Monitor Lock).
要實現這個目標,則每個java對象都應該與某種類型的鎖數據關聯。
這就意味著,我們需要一個存儲鎖數據的地方,并且每一個對象都應該有這么個地方。
在java中,這個地方就是對象頭。
其實Java的對象頭和對象的關系很像Http請求的http header和http body的關系。
對象頭中存儲了該對象的metadata, 除了該對象的鎖信息,還包括指向該對象對應的類的指針,對象的hashcode, GC分代年齡等,在對象頭這個寸土寸金的地方,根據鎖狀態的不同,有些內存是大家公用的,在不同的鎖狀態下,存儲不同的信息,而對象頭中存儲鎖信息的那部分字段,我們稱作Mark Word, 這個我們就不展開了講了。我們只需要知道:
鎖信息存儲在對象頭的Mark Word中
在synchronized鎖中,這個存儲在對象頭的Mark Word中的鎖信息是一個指針,它指向一個monitor對象(也稱為管程或監視器鎖)的起始地址。這樣,我們就通過對象頭,將每一個對象與一個monitor關聯了起來,它們的關系如下圖所示:
(圖片來源: Evaluating and improving biased locking in the HotSpot virtual machine)
圖片的最左邊是線程的調用棧,它引用了堆中的一個對象,該對象的對象頭部分記錄了該對象所使用的監視器鎖,該監視器鎖指向了一個monitor對象。
那么這個monitor對象是什么呢? 在Java虛擬機(HotSpot)中,monitor是由ObjectMonitor實現的,其主要數據結構如下: (源碼在這里)
ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
上面這些字段中,我們只需要重點關注三個字段:
_owner : 當前擁有該 ObjectMonitor 的線程
_EntryList: 當前等待鎖的集合
_WaitSet: 調用了Object.wait()方法而進入等待狀態的線程的集合,即我們上一篇一直提到的wait set
在java中,每一個等待鎖的線程都會被封裝成ObjectWaiter對象,當多個線程同時訪問一段同步代碼時,首先會被扔進 _EntryList 集合中,如果其中的某個線程獲得了monitor對象,他將成為 _owner,如果在它成為 _owner之后又調用了wait方法,則他將釋放獲得的monitor對象,進入 _WaitSet集合中等待被喚醒。
(圖片來源: Inter-thread communication in Java)
另外,因為每一個對象都可以作為synchronized的鎖,所以每一個對象都必須支持wait(),notify,notifyAll方法,使得線程能夠在一個monitor對象上wait, 直到它被notify。這也就解釋了這三個方法為什么定義在了Object類中——這樣,所有的類都將持有這三個方法。
總結:每一個java對象都和一個ObjectMonitor對象相關聯,關聯關系存儲在該java對象的對象頭里的Mark Word中。
每一個試圖進入同步代碼塊的線程都會被封裝成ObjectWaiter對象,它們或在ObjectMonitor對象的_EntryList中,或在 _WaitSet 中,等待成為ObjectMonitor對象的owner. 成為owner的對象即獲取了監視器鎖。
所以,說是每一個java對象都可以作為鎖,其實是指將每一個java對象所關聯的ObjectMonitor作為鎖,更進一步是指,大家都想成為 某一個java對象所關聯的、ObjectMonitor對象的、_owner,所以你可以把這個_owner看做是鐵王座,所有等待在這個監視器鎖上的線程都想坐上這個鐵王座,誰擁有了它,誰就有進入由它鎖住的同步代碼塊的權利。
附加題其實,了解到上面這個程度已經足夠用了,如果你想再深入的了解,例如synchronized在字節碼層面的具體語義實現,這里推薦幾篇博客:
Moniter的實現原理
OpenJDK9 Hotspot : synchronized 淺析
深入理解Java并發之synchronized實現原理
死磕Java并發—–深入分析synchronized的實現原理
另外,如果你想深入了解偏向鎖,輕量級鎖,以及鎖膨脹的過程,強烈建議看下面這篇論文:
Evaluating and improving biased locking in the HotSpot virtual machine
該篇論文的介紹非常詳細,關鍵是有很多圖示,對于Mark Word在不同鎖狀態的描述很清晰。
(完)
查看更多系列文章:系列文章目錄
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76728.html
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學習經歷。因為寫作的時候發現,為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
摘要:在從返回前,線程與其他線程競爭重新獲得鎖。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。當線程呈狀態,調用線程對象的方法會出現異常。在執行同步代碼塊過程中,遇到異常而導致線程終止,鎖也會被釋放。 方法wait()的作用是使當前執行代碼的線程進行等待,wait()方法是Object類的方法,該方法用來將當前線程置入預執行隊列中,并且在wait()所在的代碼行處停止執行,直...
摘要:前言同步代碼塊是中最基礎的實現線程間的同步與通信的機制之一,本篇我們將對同步代碼塊以及監視器鎖的概念進行討論。離開同步代碼塊后,所獲得的鎖會被自動釋放。 前言 同步代碼塊(Synchronized Block) 是java中最基礎的實現線程間的同步與通信的機制之一,本篇我們將對同步代碼塊以及監視器鎖的概念進行討論。 系列文章目錄 什么是同步代碼塊(Synchronized Block)...
摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態與線程組。 寫在前面 花了一周時...
閱讀 2263·2021-09-30 09:48
閱讀 3633·2021-09-24 10:27
閱讀 1790·2021-09-22 15:32
閱讀 2025·2021-08-09 13:44
閱讀 3575·2019-08-30 15:55
閱讀 1044·2019-08-29 17:12
閱讀 2000·2019-08-29 17:05
閱讀 2917·2019-08-29 13:43