系列文章目錄 java
前面兩篇文章咱們介紹了synchronized同步代碼塊以及wait和notify機制,大體知道了這些關鍵字和方法是幹什麼的,以及怎麼用。git
可是,知其然,並不知其因此然。github
例如:segmentfault
wait set
?本篇咱們未來解答這些問題。數據結構
總的來講,鎖有兩種不一樣的實現方式,一種是自旋,一種是掛起。併發
(suspend-lock不知道怎麼翻譯,感受叫"掛起鎖"或"懸掛鎖"都太難聽了,後面就直接不翻譯了) 性能
自旋鎖是一種樂觀鎖,它樂觀地認爲鎖資源沒有被佔用,或者即便被佔用了,也很快就會被釋放, 因此當它發現鎖已經被佔用後,大多會在原地忙等待(通常是在死循環中等待,這也就是自旋的由來), 直到鎖被釋放,咱們在以前分析AQS的文章中提過,AQS處在阻塞隊列頭部的線程用的就是自旋的方式來等待鎖。lua
suspend-lock是一種悲觀鎖,它悲觀地認爲鎖競爭老是常常發生的,若是鎖被佔用了,基本短期內不會釋放,因此他會讓出CPU資源,直接掛起,等待條件知足後,別人將本身喚醒。 spa
自旋鎖的優勢是實現簡單,只須要很小的內存,在競爭很少的場景中性能很好。可是若是鎖競爭不少,那麼大量的時間會浪費在無心義的自旋等待上,形成CPU利用率下降。操作系統
suspend-lock的優勢是CPU利用率高,由於在發現鎖被佔用後,它會當即釋放本身剩下的CPU時間隙(time-slice)給其餘線程,以指望得到更高的CPU利用率。可是由於線程的掛起與喚醒須要經過操做系統調用來完成,這涉及到用戶空間和內核空間的轉換,線程上下文的切換,因此即便在競爭不多的場景中,這種鎖也會顯得很慢。可是若是鎖競爭很激烈,則這種鎖就能夠得到很好的性能。
因而可知,自旋鎖和suspend-lock
各有優劣,他們分別適用於競爭很少和競爭激烈的場景中。
在實際的應用中,咱們能夠綜合這兩種方式的優勢,例如AQS中,排在阻塞隊列第一位的使用自旋等待,而排在後面的線程則掛起。
而咱們今天要講的synchronized,使用的是suspend-lock方式。
既然前面提到了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; }
上面這些字段中,咱們只須要重點關注三個字段:
wait set
在java中,每個等待鎖的線程都會被封裝成ObjectWaiter對象,當多個線程同時訪問一段同步代碼時,首先會被扔進 _EntryList 集合中,若是其中的某個線程得到了monitor對象,他將成爲 _owner
,若是在它成爲 _owner
以後又調用了wait
方法,則他將釋放得到的monitor對象,進入 _WaitSet集合中等待被喚醒。
(圖片來源: Inter-thread communication in Java)
另外,由於每個對象均可以做爲synchronized的鎖,因此每個對象都必須支持wait()
,notify
,notifyAll
方法,使得線程可以在一個monitor對象上wait, 直到它被notify。這也就解釋了這三個方法爲何定義在了Object類中——這樣,全部的類都將持有這三個方法。
Mark Word
中。因此,說是每個java對象均可以做爲鎖,實際上是指將每個java對象所關聯的ObjectMonitor做爲鎖,更進一步是指,你們都想成爲 某一個java對象所關聯的
、ObjectMonitor對象的
、_owner
,因此你能夠把這個_owner
看作是鐵王座,全部等待在這個監視器鎖上的線程都想坐上這個鐵王座,誰擁有了它,誰就有進入由它鎖住的同步代碼塊的權利。
其實,瞭解到上面這個程度已經足夠用了,若是你想再深刻的瞭解,例如synchronized在字節碼層面的具體語義實現,這裏推薦幾篇博客:
另外,若是你想深刻了解偏向鎖,輕量級鎖,以及鎖膨脹的過程,強烈建議看下面這篇論文:
該篇論文的介紹很是詳細,關鍵是有不少圖示,對於Mark Word在不一樣鎖狀態的描述很清晰。
(完)
查看更多系列文章:系列文章目錄