最近在看《Java并發(fā)編程的藝術(shù)》伦籍,看到一個(gè)常見的面試點(diǎn) Synchronized實(shí)現(xiàn)原理嫩舟,這塊知識(shí)工作中也用不到声功,但是我們還是有必要了解下這個(gè)醒串,畢竟我們是專業(yè)的执桌。
本文主體思路
1.Monitor
2.實(shí)例對(duì)象的存儲(chǔ)構(gòu)成
3.偏向鎖、輕量鎖芜赌、重量鎖
4.鎖的膨脹過(guò)程
5.鎖的對(duì)比
1.Monitor
顧名思義仰挣,對(duì)象的監(jiān)視器,只要發(fā)生同步操作缠沈,線程就為當(dāng)前對(duì)象創(chuàng)建一個(gè)Monitor對(duì)象與之關(guān)聯(lián)膘壶,Monitor只能被一個(gè)線程持有,此時(shí)當(dāng)前對(duì)象就處于鎖定狀態(tài)洲愤,其它線程只能阻塞等待颓芭。
Java虛擬機(jī)(HotSpot)中,Monitor是通過(guò)ObjectMonitor實(shí)現(xiàn)的(c++)柬赐,里面有三個(gè)重要的屬性
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個(gè)數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //處于wait狀態(tài)的線程亡问,會(huì)被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程,會(huì)被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
Monitor 有兩個(gè)隊(duì)列 _WaitSet 和 _EntryList肛宋,存儲(chǔ)ObjectWaiter列表(所有等待的線程都會(huì)被包裝成ObjectWaiter)州藕;① 線程申請(qǐng)owner Monitor對(duì)象,首先會(huì)被加入到 _EntryList 酝陈;② 線程申請(qǐng)owner Monitor對(duì)象床玻,進(jìn)入到 Owner區(qū)域,此時(shí)_count +1沉帮; ③線程調(diào)用wait方法锈死,釋放鎖贫堰,進(jìn)入到 _WaitSet ,此時(shí)_count -1 ④ 線程再次申請(qǐng)owner ⑤ 線程處理完畢后釋放資源并退出待牵。
Synchronized 鎖的管理就是依托于Monitor严嗜,當(dāng)線程owner Monitor的時(shí)候則擁有進(jìn)行同步操作的權(quán)利,線程進(jìn)入同步塊調(diào)用monitorenter
指令洲敢,退出同步塊則調(diào)用monitorexit
漫玄,釋放對(duì)Monitor的持有。
如何獲取鎖也就是如何owner Monitor呢压彭?
2.實(shí)例對(duì)象構(gòu)成
對(duì)象頭
主要存放 MarkWord睦优,MarkWord里存儲(chǔ)了對(duì)象的hashcode 以及鎖信息等。除了MarkWord 對(duì)象頭里還存放類的元信息--Class對(duì)象的指針(內(nèi)存地址)壮不。如果數(shù)組的話汗盘,還會(huì)再存儲(chǔ)下數(shù)組的長(zhǎng)度。
實(shí)例數(shù)據(jù)
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息询一,也是程序代碼中所定義的各種類型的字段內(nèi)容隐孽。包括從父類繼承來(lái)的,都會(huì)記錄下來(lái)健蕊。
對(duì)齊填充
HotSpot要求對(duì)象的起止地址(姑且認(rèn)為是對(duì)象大小)必須是8的整數(shù)倍菱阵,對(duì)象頭部分正好是8的倍數(shù),因此當(dāng)實(shí)例數(shù)據(jù)部分不是8的倍數(shù)的話就需要填充了缩功。
3.偏向鎖晴及、輕量鎖、重量鎖
JavaSE1.6為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗嫡锌,引入了偏向鎖虑稼、輕量鎖。而鎖的信息則是在對(duì)象頭的MarkWord里势木。Mark Word的存儲(chǔ)內(nèi)容會(huì)隨著鎖的變化而變化蛛倦,下面的表格就是不同的鎖狀態(tài)對(duì)應(yīng)的存儲(chǔ)內(nèi)容。鎖的變化歸根結(jié)底還是線程改寫Mark Word的操作啦桌。
3.1偏向鎖
HotSpot 作者經(jīng)過(guò)研究發(fā)現(xiàn)溯壶,大多數(shù)情況下,鎖不僅不存在競(jìng)爭(zhēng)而且總由同一個(gè)線程獲得震蒋,為了讓線程獲取鎖的代價(jià)更低茸塞,引入了偏向鎖。
偏向鎖的獲取
case1
當(dāng)前是無(wú)鎖狀態(tài)查剖,是則將自己的線程id钾虐,寫入到Mark Word,同時(shí)是否是偏向鎖
寫入1笋庄,當(dāng)前線程持有該對(duì)象的偏向鎖
case2
當(dāng)前對(duì)象已經(jīng)是偏向鎖效扫,判斷Mark Word里存的 線程id是不是自己的倔监,如果是則繼續(xù)持有偏向鎖
case3
當(dāng)前對(duì)象已經(jīng)是偏向鎖,并且Mark Word里存的 線程id也不是自己的菌仁,當(dāng)前線程用過(guò)CAS嘗試將Mark Word里的線程id改寫成自己浩习,如果改寫成功則當(dāng)前線程持有偏向鎖
偏向鎖的撤銷
上面case3,如果當(dāng)前線程嘗試改寫Mark Word的線程ID為自己济丘,改寫失敗谱秽,等待進(jìn)入全局安全點(diǎn)的時(shí)候,它(個(gè)人覺得是JVM)首先暫停原持有偏向鎖的線程摹迷,然后檢查原持有偏向鎖的線程是否處于不活動(dòng)或者退出同步疟赊,是則當(dāng)前線程將自己線程id寫入Mark Word,持有偏向鎖峡碉。否則當(dāng)前線程將對(duì)象標(biāo)記為輕量級(jí)鎖近哟。原持有偏向鎖的線程恢復(fù)執(zhí)行,執(zhí)行完畢退出同步塊并喚醒暫停的線程鲫寄。
關(guān)閉偏向鎖配置
java6和7里默認(rèn)是開啟偏向鎖的吉执,如果你確定應(yīng)用程序里所有的鎖通常是處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)jvm參數(shù)配置關(guān)閉偏向鎖
XX:-UseBiasedLocking=false
3.2 輕量鎖
輕量級(jí)鎖加鎖
線程執(zhí)行同步塊之前地来,JVM會(huì)先在當(dāng)前線程的棧楨中穿件存儲(chǔ)鎖記錄的空間戳玫,并將對(duì)象頭的Mark Word 復(fù)制到鎖記錄,官方稱 Displaced Mark Word靠抑。然后線程嘗試將對(duì)象頭Mark Word替換為指向鎖記錄的指針也就是Displaced Mark Word的內(nèi)存地址量九,同時(shí)改寫鎖標(biāo)識(shí)為00适掰。如果替換成功則持有偏向鎖颂碧。如果失敗則嘗試自旋獲取,自旋有次數(shù)限制类浪,超過(guò)后則膨脹為重量級(jí)鎖载城。
輕量級(jí)鎖解鎖
輕量級(jí)解鎖,會(huì)通過(guò)CAS將Displaced Mark Word
替換回到對(duì)象頭费就,如果成功則沒(méi)有競(jìng)爭(zhēng)诉瓦,退出同步塊。失敗則說(shuō)明存在競(jìng)爭(zhēng)力细,膨脹為重量級(jí)鎖睬澡。
3.3 重量鎖
這個(gè)則是最原始的鎖競(jìng)爭(zhēng)則阻塞,鎖釋放則喚醒
3.4 幾個(gè)鎖對(duì)應(yīng)的Mark Word的變化
4.鎖的膨脹過(guò)程
對(duì)象的鎖會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)眠蚂,但是不能降級(jí)煞聪。
前面簡(jiǎn)單通過(guò)文字描述了下 偏向鎖、輕量鎖逝慧、重量鎖的各自操作昔脯,不過(guò)還是一個(gè)完整的流程圖看著更為清晰啄糙。
如果還是不理解,強(qiáng)烈建議看下http://www.reibang.com/p/afa5296a4832云稚,這篇文章通過(guò)小故事來(lái)形象的解釋隧饼。
5.鎖的對(duì)比
6.最后
Synchronized實(shí)現(xiàn)原理,網(wǎng)上文章一大堆静陈,我也只是作為一個(gè)網(wǎng)絡(luò)知識(shí)的搬運(yùn)工燕雁,帶上的自己的理解,產(chǎn)出這篇文章鲸拥。其實(shí)自己也并不是100%的完全能說(shuō)清楚贵白,不過(guò)大致的流程算是知道了。如果碰到哪里不明白或者覺得哪里描述的有問(wèn)題的朋友崩泡,歡迎大家留言一起交流禁荒。
參考資料