Synchronized 的實現原理
最近在看《Java併發程式設計的藝術》,看到一個常見的面試點 Synchronized實現原理,這塊知識工作中也用不到,但是我們還是有必要了解下這個,畢竟我們professional。
本文主體思路
1.Monitor
2.例項物件的儲存構成
3.偏向鎖、輕量鎖、重量鎖
4.鎖的膨脹過程
5.鎖的對比
1.Monitor
顧名思義,物件的監視器,只要發生同步操作,執行緒就為當前物件建立一個Monitor物件與之關聯,Monitor只能被一個執行緒持有,此時當前物件就處於鎖定狀態,其它執行緒只能阻塞等待。
Java虛擬機器(HotSpot)中,Monitor是通過ObjectMonitor實現的(c++),裡面有三個重要的屬性
ObjectMonitor() { _header= NULL; _count= 0; //記錄個數 _waiters= 0, _recursions= 0; _object= NULL; _owner= NULL; _WaitSet= NULL; //處於wait狀態的執行緒,會被加入到_WaitSet _WaitSetLock= 0 ; _Responsible= NULL ; _succ= NULL ; _cxq= NULL ; FreeNext= NULL ; _EntryList= NULL ; //處於等待鎖block狀態的執行緒,會被加入到該列表 _SpinFreq= 0 ; _SpinClock= 0 ; OwnerIsThread = 0 ; }
Monitor 有兩個佇列 _WaitSet 和 _EntryList,儲存ObjectWaiter列表(所有等待的執行緒都會被包裝成ObjectWaiter);① 執行緒申請owner Monitor物件,首先會被加入到 _EntryList ;② 執行緒申請owner Monitor物件,進入到 Owner區域,此時_count +1; ③執行緒呼叫wait方法,釋放鎖,進入到 _WaitSet ,此時_count -1 ④ 執行緒再次申請owner ⑤ 執行緒處理完畢後釋放資源並退出。

Synchronized 鎖的管理就是依託於Monitor,當執行緒owner Monitor的時候則擁有進行同步操作的權利,執行緒進入同步塊呼叫 monitorenter
指令,退出同步塊則呼叫 monitorexit
,釋放對Monitor的持有。
如何獲取鎖也就是如何owner Monitor呢?
2.例項物件構成

例項物件結構
物件頭
主要存放 MarkWord,MarkWord裡儲存了物件的hashcode 以及鎖資訊等。除了MarkWord 物件頭裡還存放類的元資訊--Class物件的指標(記憶體地址)。如果陣列的話,還會再儲存下陣列的長度。

例項資料
例項資料部分是物件真正儲存的有效資訊,也是程式程式碼中所定義的各種型別的欄位內容。包括從父類繼承來的,都會記錄下來。
對齊填充
HotSpot要求物件的起止地址(姑且認為是物件大小)必須是8的整數倍,物件頭部分正好是8的倍數,因此當例項資料部分不是8的倍數的話就需要填充了。
3.偏向鎖、輕量鎖、重量鎖
JavaSE1.6為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了偏向鎖、輕量鎖。而鎖的資訊則是在物件頭的MarkWord裡。Mark Word的儲存內容會隨著鎖的變化而變化,下面的表格就是不同的鎖狀態對應的儲存內容。

MarkWord 不同鎖狀態對應的儲存
鎖的變化歸根結底還是執行緒改寫Mark Word的操作。
3.1偏向鎖

HotSpot 作者經過研究發現,大多數情況下,鎖不僅不存在競爭而且總由同一個執行緒獲得,為了讓執行緒獲取鎖的代價更低,引入了偏向鎖。
偏向鎖的獲取
case1
當前是無鎖狀態,是則將自己的執行緒id,寫入到Mark Word,同時 是否是偏向鎖
寫入1,當前執行緒持有該物件的偏向鎖
case2
當前物件已經是偏向鎖,判斷Mark Word裡存的 執行緒id是不是自己的,如果是則繼續持有偏向鎖
case3
當前物件已經是偏向鎖,並且Mark Word裡存的 執行緒id也不是自己的,當前執行緒用過CAS嘗試將Mark Word裡的執行緒id改寫成自己,如果改寫成功則當前執行緒持有偏向鎖
偏向鎖的撤銷
上面case3,如果當前執行緒嘗試改寫Mark Word的執行緒ID為自己,改寫失敗,等待進入全域性安全點的時候,它(個人覺得是JVM)首先暫停原持有偏向鎖的執行緒,然後檢查原持有偏向鎖的執行緒是否處於不活動或者退出同步,是則當前執行緒將自己執行緒id寫入Mark Word,持有偏向鎖。否則當前執行緒將物件標記為輕量級鎖。原持有偏向鎖的執行緒恢復執行,執行完畢退出同步塊並喚醒暫停的執行緒。
關閉偏向鎖配置
java6和7裡預設是開啟偏向鎖的,如果你確定應用程式裡所有的鎖通常是處於競爭狀態,可以通過jvm引數配置關閉偏向鎖
XX:-UseBiasedLocking=false
3.2 輕量鎖

輕量級鎖加鎖
執行緒執行同步塊之前,JVM會先在當前執行緒的棧楨中穿件儲存鎖記錄的空間,並將物件頭的Mark Word 複製到鎖記錄,官方稱 Displaced Mark Word。然後執行緒嘗試將物件頭Mark Word替換為指向鎖記錄的指標也就是Displaced Mark Word的記憶體地址,同時改寫鎖標識為00。如果替換成功則持有偏向鎖。如果失敗則嘗試自旋獲取,自旋有次數限制,超過後則膨脹為重量級鎖。
輕量級鎖解鎖
輕量級解鎖,會通過CAS將 Displaced Mark Word
替換回到物件頭,如果成功則沒有競爭,退出同步塊。失敗則說明存在競爭,膨脹為重量級鎖。
3.3 重量鎖
這個則是最原始的鎖競爭則阻塞,鎖釋放則喚醒
3.4 幾個鎖對應的Mark Word的變化

鎖與Mark Word內容
4.鎖的膨脹過程
物件的鎖會隨著競爭情況逐漸升級,但是不能降級。

鎖升級
前面簡單通過文字描述了下 偏向鎖、輕量鎖、重量鎖的各自操作,不過還是一個完整的流程圖看著更為清晰。

如果還是不理解,強烈建議看下 https://www.jianshu.com/p/afa5296a4832 ,這篇文章通過小故事來形象的解釋。
5.鎖的對比

6.最後
Synchronized實現原理,網上文章一大堆,我也只是作為一個網路知識的搬運工,帶上的自己的理解,產出這篇文章。其實自己也並不是100%的完全能說清楚,不過大致的流程算是知道了。如果碰到哪裡不明白或者覺得哪裡描述的有問題的朋友,歡迎大家留言一起交流。
參考資料