Java併發-synchronized與原子操作的實現原理

synchronized

Java中的每一個對象都可以作爲鎖
1.對於普通同步方法,鎖是當前實例對象
2.對於靜態同步方法,鎖是當前類的Class對象
3.對於同步方法塊,鎖是Synchronized括號裏配置的對象
當一個線程駛入訪問同步塊時,它首先必須得到鎖。退出或者拋出異常時必須釋放鎖。

實現原理

JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步,但實現細節不一樣。
代碼塊同步是使用monitorenter和monitorexit指令實現的,而方法同步是使用另外一種方式實現的。方法的同步同樣可以使用這兩個指令來實現。

monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯,當一個monitor被持有後,它將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象對應的monitor的所有權,即嘗試獲得對象的鎖。

Java對象頭

Java對象頭裏的Mark Word裏默認存儲對象的HashCode,分代年齡和鎖標記位

鎖的升級與對比

鎖一共有4種狀態:無鎖狀態,偏向鎖狀態,輕量級鎖狀態,重量級鎖狀態
鎖可以升級但不能降級。這種鎖升級卻不能降級的策略,是爲了提高獲得鎖和釋放鎖的效率

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

偏向鎖在Java6和Java7裏默認啓用的,但是它在應用程序啓動幾秒鐘之後才激活。可以使用JVM參數來關閉延遲-XX:BiasedLockingStartupDelay=0.如果確定應用程序裏所有的鎖通常情況下處於競爭狀態,可以JVM參數關閉偏向鎖-XX:-UseBiasedLocking=false.那麼程序默認會進入輕量級鎖狀態

2.輕量級鎖
輕量級加鎖,線程在執行同步塊之前,JVM會現在當前線程的棧幀中創建用於存儲記錄的空間,並將對象頭中的MarkWord複製到鎖記錄中。然後線程嘗試使用CAS將對象頭中的MarkWord替換爲指向鎖記錄的執政。如果成功,獲得鎖,失敗,表示其他線程競爭鎖,當前鮮橙該便嘗試使用自旋來獲取鎖。
輕量級解鎖,使用CAS替換回到對象頭,如果成功,則表示沒有發生競爭,失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

原子操作的實現原理

緩存行(Cache line)緩存的最小操作單位
比較並交換(Compare and Swap)操作期間舊值沒有發生變化,才交換成新值
CPU流水線(CPU pipeline)CPU流水線的工作方式就像工業生產上的裝配流水線,在CPU中由5-6個不同功能的電路單元組成一條指令處理流水線,然後將一條X86指令分成5-6步後在由這些電路單元分別執行,這樣就能實現在一個CPU時鐘週期完成一條指令,因此提高CPU的運算速度
內存順序衝突(Memory order violation)內存順序衝突一般是由假共享引起的,假共享指的就是多個CPU同時修改同一個緩存行的不同部分而引起其中一個CPU的操作無效,當出現這個內存順序衝突時,CPU必須清空流水線

處理器提供總線鎖定和緩存鎖定來保證複雜內存操作的原子性
總線鎖(處理器提供的一個Lock#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞,那麼處理器可以獨佔共享內存)
緩存鎖(在同一時刻,我們只需要保證對某個內存地址的操作是原子性即可,但是總線鎖定把CPU和內存之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據,所以總線鎖定的開銷比較大。指內存區域如果被緩存在處理器的緩存行中,並且在Lock操作期間被鎖定,那麼當它執行鎖操作回寫到內存時,處理器不在總線上LOCK#信號,而是修改內部內存地址,並循序它的緩存一致性機制來保證操作的原子性,因爲緩存一致性機制會組織同時修改由兩個以上處理器緩存的內存區域數據,當其他處理器回寫已被鎖定的緩存行的數據時,會使緩存行無效)
2種情況必須使用總線
1.當操作的數據不能被緩存在處理器內部或操作的數據跨多個緩存行時,則處理器會調用總線鎖定
2.有些處理器不支持緩存鎖定

Java實現原子操作

1.使用循環CAS實現原子操作
JVM的CAS操作(自旋CAS實現的基本思路就是循環進行CAS操作知道成功爲止)
2.CAS實現原子操作的三大問題
a.ABA問題(使用版本號解決)AtomicStampedReference
b.循環時間長開銷大(如果CAS自旋長時間不成功,會給CPU帶來非常大的執行開銷)
c.只能保證一個共享變量的原子操作(可以用AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象裏進行CAS操作)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章