1. 程式人生 > >java多執行緒程式設計(二)

java多執行緒程式設計(二)

java多執行緒程式設計(二)

     volatile、synchronized及鎖對比

volatile的使用優化

1、追加位元組能優化效能

如果佇列的頭節點和尾節點都不足64位元組的話,處理器會將他們都讀到同一個快取記憶體行中,在多處理器
下每個處理器都會快取同樣的頭、尾節點,當一個處理器試圖修改頭節點時,會將整個快取行鎖定,會導致其他
處理器不能訪問自己快取記憶體中的尾節點。
通過填充位元組來填滿快取記憶體行,避免頭節點和尾節點載入到同一個快取行,使其在修改時不會互相鎖定。

什麼情況下避免使用這種方法:
快取行非64位元組寬的處理器
共享變數不會頻繁地寫
!在java7下可能不會生效

二、synchronized 實現原理與應用:

又稱為重量級鎖:

java中的每一個物件都可以作為鎖:

1、對於普通的同步方法,鎖是當前例項物件

2、對於靜態的同步方法,鎖是當前類的Class物件

3、對於同步方法塊,鎖是Synchonized括號裡配置的物件

從JVM規範中可以看到Synchonized在JVM裡的實現原理,JVM基於進入和退出Moniter物件來實現方法同步和程式碼塊同步

但兩者的實現細節不一樣。

程式碼塊同步是使用monitorenter 和 monitorexit指令實現的;

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

2.2.1 Java物件頭

synchronized用的鎖儲存在物件頭中,陣列物件用三個位元組寬儲存物件頭,如果物件是非陣列型別,則用2個位元組寬儲存物件
1個位元組寬等於4位元組即32bit

java物件頭的長度:

32/64bit Mark Word 儲存物件的hashCode或鎖資訊

32/64bit Class Metadata Address 儲存物件型別資料的指標

32/64bit Array length 陣列的長度(如果當前物件是陣列)

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

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

Mark Word的狀態變化:
在這裡插入圖片描述

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

在這裡插入圖片描述

2.2.2鎖的升級與對比:

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

HotSpot作者認為大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同
一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。

當一個執行緒訪問同步塊並
獲取鎖時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入和退出
同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下物件頭的Mark Word裡是否
儲存著指向當前執行緒的偏向鎖。

如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需
要再測試一下Mark Word中偏向鎖的標識是否設定成1(表示當前是偏向鎖):如果沒有設定,則
使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。

(1)偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他執行緒嘗試競爭偏向鎖時,
持有偏向鎖的執行緒才會釋放鎖。偏向鎖的撤銷,需要等待全域性安全點(在這個時間點上沒有正
在執行的位元組碼)。它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,
如果執行緒不處於活動狀態,則將物件頭設定成無鎖狀態;如果執行緒仍然活著,擁有偏向鎖的棧
會被執行,遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的Mark Word要麼重新偏向於其他
執行緒,要麼恢復到無鎖或者標記物件不適合作為偏向鎖,最後喚醒暫停的執行緒。圖2-1中的線
程1演示了偏向鎖初始化的流程,執行緒2演示了偏向鎖撤銷的流程。
在這裡插入圖片描述

(2)關閉偏向鎖

偏向鎖在Java 6和Java 7裡是預設啟用的,但是它在應用程式啟動幾秒鐘之後才啟用,如
有必要可以使用JVM引數來關閉延遲:-XX:BiasedLockingStartupDelay=0。如果你確定應用程
序裡所有的鎖通常情況下處於競爭狀態,可以通過JVM引數關閉偏向鎖:-XX:-
UseBiasedLocking=false,那麼程式預設會進入輕量級鎖狀態。

2.輕量級鎖

(1)輕量級鎖加鎖

執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並
將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用
CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失
敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。

(2)輕量級鎖解鎖

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到物件頭,如果成
功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。圖2-2是
兩個執行緒同時爭奪鎖,導致鎖膨脹的流程圖。

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的執行緒被阻塞住了),一旦鎖升級
成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他執行緒試圖獲取鎖時,
都會被阻塞住,當持有鎖的執行緒釋放鎖之後會喚醒這些執行緒,被喚醒的執行緒就會進行新一輪
的奪鎖之爭。
在這裡插入圖片描述
什麼是CAS操作
參看如下:
https://blog.csdn.net/qq_37937537/article/details/82798218

3.鎖的優缺點對比
在這裡插入圖片描述
2.3 原子操作的實現原理
原子(atomic)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意
為“不可被中斷的一個或一系列操作”。

在多處理器上實現原子操作就變得有點複雜。