JAVA的多線程與高併發(二)、Synchronized

Synchronized

Synchronized涉及不少知識面,咱們先從一些相關知識講起。java

CAS

CAS 全稱 Compare And Swap (又稱Compare And Exchange) / 自旋 / 自旋鎖 / 無鎖程序員

由於常常配合循環操做,直到修改完成爲止,因此泛指一類操做算法

執行原理

CAS算法執行原理

細節事項

一、保證原子性

​ CAS操做雖然能夠作到不須要主動上鎖就能夠解決原子操做問題。可是其實在執行對比A與V的值時,在底層仍然是加鎖了的,否則沒法避免指令亂序執行而致使的數據不一致,只是這個操做對咱們程序員是無感知的。安全

這裏拓展一下,在CAS的底層實現中,(多核)CPU實際調用了指令 lock cmpxch。事實上也是經過上鎖來保證CAS操做的原子性,只不過這個鎖實際鎖定的是北橋信號而不是內存總線,效率較高些。多線程

​ 具體能夠參考文章CAS你覺得你真的懂?app

二、ABA問題

​ ABA問題能夠理解爲,待修改對象值本來是A被修改成B後又改成了A,那麼此時再單純的對比對象值就沒辦法肯定是否應該更新了。jvm

​ 解決辦法使用版本號來肯定修改版本(版本號 AtomicStampedReference),若是是基礎類型簡單值則不須要版本號。佈局

內核態與用戶態

這個是操做系統中的一個概念。操做系統爲了不應用程序直接使用內核中的服務,把內核空間與用戶空間進行了分割。這也表明着,應用程序(如JVM)向操做系統申請內核服務或資源時,須要花費更多的時間與資源。優化

對synchronized有什麼影響呢?

在早期JDK版本中實現synchronized,是經過向操做系統申請重量級鎖的方式實現的。而申請重量級鎖就必須經過操做系統的內核的容許再進行系統調用(以中斷的方式),如此一來,致使早期synchronied的實現特別低效。操作系統

注:*中斷和異常可使用系統調用來調用內核方法。

後來出現的偏向鎖和輕量級鎖(自旋鎖)在用戶態中處理,因此效率也高了不少。

Markword

講到Java的markword那就必需要講到Java對象的內存佈局了。

當咱們建立一個對象,對象信息會被jvm寫入內存,對象在內存中的分佈以下圖(如下是64位的jvm環境,默認開啓壓縮指針的狀況)

markword

8字節對齊

咱們稱markword+class pointer = 12Byte ,這12字節能夠稱做object header。它管理着一個對象的各類屬性狀態。object header後面的內容則是對象的成員變量。

JVM對內存的管理有8字節對齊的要求,因此咱們能夠看到一、3圖中都由於原始內容不足8*倍而進行了補齊填充。

可是他們的狀況有點不同,一個是在類成員後面補充,一個則是在class pointer和long類型成員間補充。因此對齊填充是有2種狀況的,這個也是我偶然發現的。。具體補齊的實現我沒有去查看細節,這個不做爲重點討論。

Markword的位數對應關係

剛剛談到了咱們稱markword+class pointer = 12Byte ,這12字節能夠稱做object header。它管理着一個對象的各類屬性狀態。

如今具體來說講Markword中與鎖相關的位對應着什麼信息。

markword-bit

咱們直接看最後三位,首先,最後兩位決定了鎖的類型

  • 0 1 :兩種可能

    • 0 0 1:無鎖,新對象

    • 1 0 1:偏向鎖

  • 0 0 :自旋鎖

  • 1 0 :重量級鎖

  • 1 1 :GC標記

鎖升級

synchronized優化的過程和markword息息相關

markword上面已經看過了每一位的表示意義,如今來理解synchronized鎖升級的過程。

lock-upgrade

  • 匿名偏向鎖

    -XX:BiasedLockingStartupDelay會設定偏向鎖的啓動延時(默認4s),當JVM啓動時會有不少線程競爭,因此默認狀況啓動時不直接打開偏向鎖,再一段時間後設爲偏向鎖,此時爲匿名偏向鎖,線程ID爲0。

  • 偏向鎖

    偏向鎖特別的指向了某個線程,看markword-bit圖中能夠看到寫入了線程指針。(通常此時毫無競爭,哪一個線程來使用這把鎖,鎖就指向哪一個線程)

  • 輕量級鎖

    又稱自旋鎖,偏向鎖在輕度競爭時會撤銷偏向鎖,升級輕量級鎖,也有可能從普通對象直接升級爲輕量級鎖。若是產生了競爭,競爭的線程們會生成一個Lock Record於本身的線程棧中,並用CAS操做將markword設置爲指向本身線程的LockRecord的指針,設置成功者獲得鎖。其他沒有獲取到鎖的線程繼續自旋。

  • 重量級鎖

    競爭加重,有線程超過10次自旋,( 由-XX:PreBlockSpin控制自旋次數,默認爲10), 或者自旋線程數超過CPU核數的一半,升級重量級鎖,向操做系統申請資源, CPU從3級到0級系統調用,線程掛起,進入等待隊列,等待操做系統的調度,而後再映射回用戶空間(JAVA1.6以後,加入自適應自旋, JVM本身控制鎖升級)

爲何有自旋鎖還須要重量級鎖?

自旋是消耗CPU資源的,若是鎖的時間長,或者自旋線程多,CPU資源會被大量消耗

重量級鎖(ObjectMonitor)有等待隊列(WaitSet),全部拿不到鎖的進入等待隊列,不須要消耗CPU資源

偏向鎖是否必定比自旋鎖效率高?

不必定,在明確知道會有多線程競爭的狀況下,偏向鎖確定會涉及鎖撤銷,這時候直接使用自旋鎖

JVM啓動過程,會有不少線程競爭(明確),因此默認狀況啓動時不打開偏向鎖,過一段兒時間再打開

若是計算過對象的hashCode,則對象沒法進入偏向狀態!

輕量級鎖重量級鎖的hashCode存在與什麼地方?

答案:線程棧中,輕量級鎖的LockRecord中,或是表明重量級鎖的ObjectMonitor的成員中

鎖重入

可重入鎖,它的可重入性表如今同一個線程能夠屢次得到鎖。

首先synchronized就是可重入鎖,也必須是可重入鎖。不然子類調用父類的super.m()的操做就沒法實現。

並且重入的次數必須被記錄,由於解鎖須要對應的重入次數。

  • 偏向鎖/輕量級鎖:記錄重入次數在線程棧中,每多一次重入LockRecord就會建立多一個,解鎖時則刪除LockRecord
  • 重量級鎖:與上面相似的操做,鎖重入信息記錄到ObjectMonitor

synchronized vs Lock (CAS)

在高爭用 高耗時的環境下synchronized效率更高
 在低爭用 低耗時的環境下CAS效率更高
 synchronized到重量級以後是等待隊列(不消耗CPU)
 CAS(等待期間消耗CPU)

鎖消除 lock eliminate

public void add(String str1,String str2){
         StringBuffer sb = new StringBuffer();
         sb.append(str1).append(str2);
}

咱們都知道 StringBuffer 是線程安全的,由於它的關鍵方法都是被 synchronized 修飾過的,但咱們看上面這段代碼,咱們會發現,sb 這個引用只會在 add 方法中使用,不可能被其它線程引用(由於是局部變量,棧私有),所以 sb 是不可能共享的資源,JVM 會自動消除 StringBuffer 對象內部的鎖。

鎖粗化 lock coarsening

public String test(String str){
       
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():
}

JVM 會檢測到這樣一連串的操做都對同一個對象加鎖(while 循環內 100 次執行 append,沒有鎖粗化的就要進行 100 次加鎖/解鎖),此時 JVM 就會將加鎖的範圍粗化到這一連串的操做的外部(好比 while 體外),使得這一連串操做只須要加一次鎖便可。

相關文章
相關標籤/搜索