1. 程式人生 > >轉 Java並發之鎖的升級

轉 Java並發之鎖的升級

watermark 描述 並發編程 官方 aced 同時 pla 一個 內容

說明:本文大部分內容來自《並發編程的藝術》,再加上自己網絡整理和理解
以下內容來自《java並發編程的藝術》作者:方鵬飛 魏鵬 程曉明

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

鎖的升級與對比

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

markword

因為偏向鎖,鎖住對象時,會寫入對象頭相應的標識,我們先把對象頭(官方叫法為:Mark Word)的圖示如下(借用了網友的圖片):
技術分享圖片

1.偏向鎖

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

上文中黑體字部分,寫得太簡略,以致於很多初學者,對這個過程有點不明白,這個過程是怎麽實現鎖的升級、釋放的?下面一一分析

  1. 線程2來競爭鎖對象;
  2. 判斷當前對象頭是否是偏向鎖;
  3. 判斷擁有偏向鎖的線程1是否還存在;
  4. 線程1不存在,直接設置偏向鎖標識為0(線程1執行完畢後,不會主動去釋放偏向鎖);
  5. 使用cas替換偏向鎖線程ID為線程2,鎖不升級,仍為偏向鎖;
  6. 線程1仍然存在,暫停線程1;
  7. 設置鎖標誌位為00(變為輕量級鎖),偏向鎖為0;
  8. 從線程1的空閑monitor record中讀取一條,放至線程1的當前monitor record中;
  9. 更新mark word,將mark word指向線程1中monitor record的指針;
  10. 繼續執行線程1的代碼;
  11. 鎖升級為輕量級鎖;
  12. 線程2自旋來獲取鎖對象;
    技術分享圖片

2、輕量級鎖

(1)輕量級鎖加鎖
線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
(2)輕量級鎖解鎖
輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。
技術分享圖片
因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

轉 Java並發之鎖的升級