多線程有序性的保證——synchronized及鎖的優化

1. 引言

實現“順序性”的主要方式就是加鎖,synchronized與多種鎖,此次內容主要講講synchronized在jdk1.6以後做的一些鎖優化。

2. synchronized同步基礎

Java中的每一個對象都可以作爲鎖,主要有以下三種:

  1. 對於普通同步方法,鎖是當前實例對象
  2. 對於靜態同步方法,鎖是當前類的Class對象
  3. 對與同步方法塊,鎖是Synchronized括號裏配置的對象

當一個線程試圖訪問同步代碼塊時,他必須首先得到鎖,推出或拋出異常時必須釋放鎖。synchronized在JVM裏的實現原理是基於進入和退出Monitor對象來實現方法同步和代碼塊同步,monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處。

3. Java對象頭

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

長度 內容 說明
32/64bit Mark Word 存儲對象對hashCode或鎖信息等
32/64bit Class Metadata Address 存儲到對象類型數據的指針
32/64bit Array length 數組的長度(如果當前對象是數組)

4. 鎖的升級與對比

Java SE 1.6爲了優化鎖的性能,增加了幾種鎖的狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。這幾種狀態會隨着競爭情況逐漸升級,鎖的狀態可以升級但不可以降級。

4.1 偏向鎖

HotSpot作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,爲了讓線程獲得鎖的代價更低而引入了偏向鎖。在無實際線程競爭,且將來只有一個線程申請鎖時,偏向鎖能最大限度提升性能,減少上下文切換以及線程阻塞過程中用戶態和核心態切換造成對性能影響。
當一個線程訪問同步塊並獲得鎖時,會在對象頭和棧幀中的鎖記錄裏存儲偏向的線程ID,以後該線程在進入和退出同步塊時,不需要進行CAS操作來加鎖和解鎖,只需要簡單地測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

4.1.1 偏向鎖的撤銷

偏向鎖使用一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。偏向鎖撤銷需要等到全局安全點,然後檢查持有偏向鎖的線程是否處於活動狀態,如果線程不處於活動狀態,則將對象頭設置成無鎖狀態;如果線程活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼重新偏向於其他線程,要麼恢復到無鎖或者標記對象不適合作爲偏向鎖,最後喚醒暫停的線程。
在這裏插入圖片描述

4.2 輕量級鎖和重量級鎖

4.2.1 輕量級鎖加鎖

線程在執行同步塊之前,JVM會先在當前線程的棧幀中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
輕量級鎖瞄準不存在鎖競爭的場景,如果存在鎖競爭但不激烈,仍然可以用自旋鎖優化,自旋失敗後再膨脹爲重量級鎖。通過自旋鎖,可以減少線程阻塞造成的線程切換(包括掛起線程和恢復線程)。如果鎖的持有時間比較短,那麼,對於競爭這些鎖的線程而言,因爲鎖阻塞造成線程切換的時間與鎖持有的時間相當,減少線程阻塞造成的線程切換,能得到較大的性能提升。具體如下:
當前線程競爭鎖失敗時,不直接阻塞自己,而是自旋(空等待,比如一個空的有限for循環)一會,在自旋的同時重新競爭鎖,如果自旋結束前獲得了鎖,那麼鎖獲取成功;否則,自旋一定次數後會阻塞自己。如果在自旋的時間內,鎖就被舊owner釋放了,那麼當前線程就不需要阻塞自己,減少了一次線程切換。

4.2.2 輕量級鎖解鎖

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。
因爲自旋會消耗CPU,爲了避免無用的自旋,一旦鎖升級到重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他線程視圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。
在這裏插入圖片描述

5. 鎖優缺點總結

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

圖片轉自:https://blog.csdn.net/qq_16410733/article/details/94004210
部分內容參考:https://www.jianshu.com/p/36eedeb3f912

發佈了86 篇原創文章 · 獲贊 25 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章