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