1. 程式人生 > >Synchronized實現原理總結

Synchronized實現原理總結

依次 mark adding ali zed 線程id 遍歷 安全 ren

synchronized實現同步的基礎:Java中的每一個對象都可以作為鎖。具體表現為以下3種形式。

  • 對於普通同步方法,鎖是當前實例對象。

  • 對於靜態同步方法,鎖是當前類的Class對象。

  • 對於同步方法塊,鎖是Synchonized括號裏配置的對象。

從JVM規範中可以看到Synchonized在JVM裏的實現原理,JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步。monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖。

synchronized用的鎖是存在Java對象頭裏的。如果對象是數組類型,則虛擬機用3個字寬(Word)存儲對象頭,如果對象是非數組類型,則用2字寬存儲對象頭。在32位虛擬機中,1字寬等於4字節,即32bit。

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

1,偏向鎖

當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,以後該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執行的字節碼)。它會首先暫停擁有偏向鎖的線程,然後檢查持有偏向鎖的線程是否活著,如果線程不處於活動狀態,則將對象頭設置成無鎖狀態;如果線程仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麽重新偏向於其他線程,要麽恢復到無鎖或者標記對象不適合作為偏向鎖,最後喚醒暫停的線程。

2,輕量級鎖

當有競爭且競爭不強烈時,JVM就會由偏向鎖膨脹為輕量級鎖,考慮到線程的阻塞和喚醒需要CPU從用戶態轉為核心態,而這種轉換對CPU來說是一件負擔很重的操作,所以沒有獲取到鎖的線程不會進入阻塞狀態,而是通過自旋的方式一直嘗試獲取鎖,處於一種忙等狀態,所以這種處理競爭的方式比較浪費CPU。

3,重量級鎖

當競爭競爭激烈時,線程直接進入阻塞狀態。不過在高版本的JVM中不會立刻進入阻塞狀態而是會自旋一小會兒看是否能獲取鎖如果不能則進入阻塞狀態。

三種鎖優缺點對比:

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


--摘自《並發編程的藝術》

Synchronized實現原理總結