1. 程式人生 > >java synchronized關鍵字在jvm中的實現原理

java synchronized關鍵字在jvm中的實現原理

為了實現共享資源的執行緒安全,我們常常會使用synchronzied關鍵字來為資源加鎖,在一個執行緒操作資源的時候,將資源鎖住只供自己使用。這樣保證了執行緒使用資源的同步,避免了對資源同時讀寫造成讀寫結果異常的情況。

那麼,synchronized關鍵字在我們的jvm中具體是怎麼實現的呢?

1.鎖標誌

當我們給一個變數或者方法加鎖的時候,jvm是如何處理變數或者方法讓它們有鎖的呢?

1.1 synchronized使用方式

當我們使用synchronized關鍵字的使用方式不同,具體的效果也不同:

  • 當我們給一個普通方法加了synchronized關鍵字的時候,鎖住的是這個方法所在類的例項物件
  • 當我們給一個static方法加鎖的時候,鎖住的是當前類的Class物件,如果不清楚Class物件是什麼,請參見我的這篇部落格:https://blog.csdn.net/qq_37856300/article/details/84854259
  • 當我們使用synchronized塊的時候,鎖住的是synchronized括號裡的物件

清楚了上面三種鎖的使用方式,可以發現三種方式鎖的都是物件,那麼問題就來了,jvm如何判斷一個物件被鎖了呢?

1.2 java物件頭

如果一個物件被鎖了,鎖的資訊是會儲存在這個物件的物件頭中的,什麼是物件頭呢?

每個java物件都有物件頭,物件的物件頭中儲存著這個物件的資訊,以32位jvm為例,一個物件的物件頭是下圖的結構的:
普通物件:
在這裡插入圖片描述


陣列物件:
在這裡插入圖片描述

我們的鎖資訊就儲存在圖中的Mark Word中,Mark Word的結構如下圖所示:
在這裡插入圖片描述

由此可見,jvm根據一個物件的物件頭中的Mark Work來判斷物件是否加鎖,但是問題又來了,從上圖中我們看到鎖的狀態可不止一種,而是足足有四種之多,其中的無鎖狀態不用多說,那麼其它三種狀態又是什麼呢?

四種鎖型別

我們使用synchronized關鍵字是為資源加一個鎖,但是實際上synchronized會根據不同情況為資源自動選擇不同的鎖,在Java SE1.6中鎖一共有4種狀態,級別從低到高分別是無鎖、偏向鎖、輕量級鎖、重量級鎖,這幾種狀態會隨著競爭情況逐漸升級,而且只可以升級但是不能降級降回來,這是為了提高獲得鎖和釋放鎖的效率。

1.偏向鎖

HotSpot虛擬機器的作者經過研究發現,大多數情況下,鎖不存在多執行緒競爭,而且總是由同一執行緒多次獲得。

如果在執行過程中,同步鎖只有一個執行緒訪問,不存在多執行緒爭用的情況,則執行緒是不需要同步的,這種情況下,就會給執行緒加一個偏向鎖。
如果在執行過程中,遇到了其他執行緒搶佔鎖,則持有偏向鎖的執行緒會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。

當一個執行緒訪問鎖並獲取鎖的時候,會在物件頭和棧幀中的鎖記錄中儲存偏向鎖偏向的執行緒的ID,以後該執行緒進出同步程式碼塊的時候不需要進行CAS加鎖解鎖,只需要去看一眼加偏向鎖的物件的Mark Word中儲存的偏向執行緒ID是否等於當前訪問的執行緒的ID。如果是,證明執行緒已經獲得了鎖;如果不是,先要去物件的MarkWord中看看當前是不是偏向鎖,如果不是,就使用CAS競爭鎖,如果是,就嘗試使用CAS將物件的偏向鎖偏向的執行緒ID改為當前執行緒的ID。

這一大段你可能已經看暈了,舉個簡單的例子,我們是老闆,開了一家餐館,一個老顧客老是來我們的餐館吃飯,而且總要同樣的菜,那麼我們就記住了,下次就省去了給他點菜的過程,直接給他上他習慣點的菜就行了。什麼時候他換口味了,老是吃別的一種菜,我們再記錄他新的愛吃的菜就行了。用通俗的話來說就是,一個物件老是被相同的執行緒獲得鎖,那就記住這個執行緒,下次直接省去了獲得鎖釋放鎖的步驟,什麼時候其它執行緒總是搶到鎖了,再去記住別的執行緒。

2.輕量級鎖

在輕量級鎖加鎖的時候,一條執行緒在執行同步程式碼塊的之前,會先在自己的執行緒幀中建立儲存鎖記錄的空間,然後將物件的Mark Word複製到鎖記錄中,接著會嘗試使用CAS操作將鎖住的物件的Mark Word替換為指向這條執行緒的鎖記錄的指標。如果替換成功了,當前執行緒獲得輕量級鎖,如果失敗了,就會一次次地重複嘗試獲取鎖,這叫做自旋,當自旋了很多次依然沒有成功的時候,鎖就會膨脹,膨脹為重量級鎖,此時如果依然沒有爭奪到資源,執行緒就會進入阻塞狀態。
當釋放一個輕量級鎖的時候,會使用CAS操作把之前線上程鎖記錄中儲存的Mark Word替換回物件頭,如果成功了,表示沒有競爭發生,如果失敗了,則表示當前鎖存在競爭,於是此執行緒在釋放輕量級鎖的時候就會喚醒等待的執行緒。

所以,我們簡簡單單地使用了一個synchronized關鍵字,但是背後jvm使用了鎖膨脹策略來優化,因為使用重量級鎖是一種比較耗費效能的事。