JVM系列之:物件的鎖狀態和同步
阿新 • • 發佈:2020-07-24
[toc]
# 簡介
鎖和同步是java多執行緒程式設計中非常常見的使用場景。為了鎖定多執行緒共享的物件,Java需要提供一定的機制來實現共享物件的鎖定,從而保證一次只有一個執行緒能夠作用於共享物件。當第二個執行緒進入同一個區域的時候,必須等待第一個執行緒解鎖該物件。
JVM是怎麼做到的呢?為了實現這個功能,java物件又需要具備什麼樣的結構呢?快來一起看看吧。
# java物件頭
Java的鎖狀態其實可以分為三種,分別是偏向鎖,輕量級鎖和重量級鎖。
在Java HotSpot VM中,每個物件前面都有一個class指標和一個Mark Word。 Mark Word儲存了雜湊值以及分代年齡和標記位等,通過這些值的變化,JVM可以實現對java物件的不同程度的鎖定。
還記得我們之前分享java物件的那張圖嗎?
![](https://img-blog.csdnimg.cn/20200618121615778.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
javaObject物件的物件頭大小根據你使用的是32位還是64位的虛擬機器的不同,稍有變化。這裡我們使用的是64位的虛擬機器為例。
Object的物件頭,分為兩部分,第一部分是Mark Word,用來儲存物件的執行時資料比如:hashcode,GC分代年齡,鎖狀態,持有鎖資訊,偏向鎖的thread ID等等。
在64位的虛擬機器中,Mark Word是64bits,如果是在32位的虛擬機器中Mark Word是32bits。
第二部分就是Klass Word,Klass Word是一個型別指標,指向class的元資料,JVM通過Klass Word來判斷該物件是哪個class的例項。
我們可以看到物件頭中的Mark Word根據狀態的不同,儲存的是不同的內容。
其中鎖標記的值分別是:無鎖=001,偏向鎖=101,輕量級鎖=000,重量級鎖=010。
# java中鎖狀態的變化
為什麼java中的鎖有三種狀態呢?其本質原因是為了提升鎖的效率,因為不同情況下,鎖的力度是不一樣的。
通過設定不同的鎖的狀態,從而可以不同的情況用不同的處理方式。
下圖是java中的鎖狀態的變化圖:
![](https://img-blog.csdnimg.cn/20200619212424286.png)
上面的圖基本上列出了java中鎖狀態的整個生命週期。接下來我們一個一個的講解。
# 偏向鎖biased locking
一般來說,一個物件被一個執行緒獲得鎖之後,很少發生執行緒切換的情況。也就是說大部分情況下,一個物件只是被一個物件鎖定的。
那麼這個時候我們可以通過設定Mark word的一定結構,減少使用CAS來更新物件頭的頻率。
為了實現這樣的目標,我們看下偏向鎖的Mark word的結構:
![](https://img-blog.csdnimg.cn/20200619215207151.png)
當偏向執行緒第一次進入同步塊的時候,會去判斷偏向鎖的狀態和thread ID,如果偏向鎖狀態是1,並且thread ID是空的話,將會使用CAS命令來更新物件的Mark word。
設定是否偏向鎖=1,鎖標記=01,執行緒ID設定為當前鎖定該物件的執行緒。
下一次該物件進入同步塊的時候,會先去判斷鎖定的執行緒ID和當前執行緒ID是否相等,如果相等的話則不需要執行CAS命令,直接進入同步塊。
如果這個時候有第二個執行緒想訪問該物件的同步塊,因為當前物件頭的thread ID是第一個執行緒的ID,跟第二個執行緒的ID不同。
如果這個時候執行緒1的同步塊已經執行完畢,那麼需要解除偏向鎖的鎖定。
解除鎖定很簡單,就是將執行緒ID設定為空,並且將偏向鎖的標誌位設為0,
如果這個時候執行緒1的同步塊還在執行,那麼需要將偏向鎖升級為輕量級鎖。
# 輕量級鎖thin lock
先看下輕量級鎖的結構:
![](https://img-blog.csdnimg.cn/20200619221814770.png)
可以看到Mark word中存放的是棧中鎖記錄的指標和鎖的標記=00。
如果物件現在處於未加鎖狀態,當一個執行緒嘗試進入同步塊的時候,會將把物件頭和當前物件的指標拷貝一份,放線上程的棧中一個叫做lock record的地方。
然後JVM通過CAS操作,將物件頭中的指標指向剛剛拷貝的lock record。如果成功,則該執行緒擁有該物件的鎖。
實際上Lock Record和Mark word形成了一個互相指向對方的情況。
下次這個執行緒再次進入同步塊的時候,同樣執行CAS,比較Mark word中的指標是否和當前thread的lock record地址一致,如果一致表明是同一個執行緒,可以繼續持有該鎖。
如果這個時候有第二個執行緒,也想進入該物件的同步塊,也會執行CAS操作,很明顯會失敗,因為物件頭中的指標和lock record的地址不一樣。
這個時候第二個執行緒就會自旋等待。
那麼第一個執行緒什麼時候會釋放鎖呢?
輕量級鎖線上程退出同步塊的時候,同樣需要執行CAS命令,將鎖標記從00替換成01,也就是無鎖狀態。
# 重量級鎖
如果第二個執行緒自旋時間太久,就會將鎖標記替換成10(重量級鎖),並且設定重量級鎖的指標,指向第二個執行緒,然後進入阻塞狀態。
當第一個執行緒退出同步塊的時候,執行CAS命令就會出錯,這時候第一個執行緒就知道鎖已經膨脹成為重量級鎖了。
第一個執行緒就會釋放鎖,並且喚醒等待的第二個執行緒。
第二個執行緒被喚醒之後,重新爭奪鎖。
我們看下重量級鎖的結構:
![](https://img-blog.csdnimg.cn/20200619224613221.png)
# 三種鎖狀態的不同
偏向鎖,輕量級鎖和重量級鎖到底有什麼不同了?
這裡總結一下,偏向鎖下次進入的時候不需要執行CAS命令,只做執行緒ID的比較即可。
輕量級鎖進入和退出同步塊都需要執行CAS命令,但是輕量級鎖不會阻塞,它使用的是自旋命令來獲取鎖。
重量級鎖不使用自旋,但是會阻塞執行緒。
好了,小夥伴們對於鎖的狀態變化有什麼疑問嗎?歡迎留言。
> 本文作者:flydean程式那些事
>
> 本文連結:[http://www.flydean.com/jvm-object-lock-synchronization/](http://www.flydean.com/jvm-object-lock-synchronization/)
>
> 本文來源:flydean的部落格
>
> 歡迎關注我的公眾號:程式那些事,更多精彩等著您!