1. 程式人生 > >synchronized的實現原理與應用

synchronized的實現原理與應用

Java程式碼在編譯後會變成Java位元組碼,位元組碼被類載入器載入到JVM裡,JVM執行位元組碼,最終需要轉化為彙編指令在CPU上執行,Java中所使用的併發機制依賴於JVM的實現和CPU的指令。

synchronized的實現原理與應用

在多執行緒併發程式設計中synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖。但是,隨著Java SE 1.6對synchronized進行了各種優化之後,有些情況下它就並不那麼重了。

先來看下利用synchronized實現同步的基礎:Java中的每一個物件都可以作為鎖。具體表現為以下3種形式:

  • 對於普通同步方法,鎖是當前例項物件。
  • 對於靜態同步方法,鎖是當前類的Class物件。
  • 對於同步方法塊,鎖是synchronized括號裡配置的物件。

當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖。那麼所到底存在哪裡呢?鎖裡面會儲存什麼資訊呢?

從JVM規範中可以看到synchronized在JVM裡的實現原理,JVM基於進入和退出Monitor物件來實現方法同步和程式碼塊同步,但兩者的實現細節不一樣。程式碼塊同步是使用monitorenter和monitorexit指令實現的,而方法同步是使用另外一種方式實現的,細節在JVM規範裡並沒有詳細說明。但是,方法的同步同樣可以使用這兩個指令來實現。

monitorenter指令是在編譯後插入到同步程式碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何物件都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。執行緒執行到monitorenter指令時,將會嘗試獲取物件所對應的monitor的所有權,即嘗試獲得物件的鎖。

Java物件頭

synchronized用的鎖是存在Java物件頭裡的。如果物件是陣列型別,則虛擬機器用3個字款(Word)儲存物件頭,如果物件是非陣列型別,則用2字寬儲存物件頭。在32位虛擬機器中,1字寬等於4位元組,即32bit,如表所示:

長度 內容 說明
32/64bit Mark Word 儲存物件的hashCode或鎖資訊等
32/64bit Class Metadata Address 儲存到物件型別資料的指標
32/32bit Array length 陣列的長度(如果當前物件是陣列)

Java物件頭裡的Mark Word裡預設儲存物件的HashCode、分代年齡和鎖標記位。32位JVM的Mark Word的預設儲存結構如表所示

鎖狀態 25bit 4bit 1bit是否是偏向鎖 2bit鎖標誌位
無鎖狀態 物件的hashCode 物件分代年齡 0 01

在執行期間,Mark Word裡儲存的資料會隨著鎖標誌位的變化而變化。Mark Word可能變化位儲存以下4種資料,如表所示:

在64位虛擬機器下,Mark Word是64bit大小的,其儲存結構如表所示:

鎖的升級與對比

Java SE 1.6為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了“偏向鎖”和“輕量級鎖”,在Java SE 1.6種,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

1.偏向鎖

HotSpot的作者經常研究發現,大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀種的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設定成1(表示當前是偏向鎖);如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。

3.鎖的優缺點對比
優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個執行緒訪問同步塊場景
輕量級鎖 競爭的執行緒不會阻塞,提高了程式的響應速度 如果始終得不到鎖競爭的執行緒,使用自旋會消耗CPU 追求響應時間同步塊執行速度非常快
重量級鎖 執行緒競爭不使用自旋,不會消耗CPU 執行緒阻塞,響應時間緩慢 追求吞吐量同步塊執行速度較長