Java多線程併發之鎖的概念/同步關鍵字/synchronized在JVM的底層是如何實現的

系列目錄

今天又是愛學習的一天哦~html

日期:2019-6-27 21:10:45java

鎖的概念

自旋鎖

  • 概念:不放棄CPU事件,不斷使用 CAS 嘗試對數據進行更新,直到成功
  • 實例:AtomicInt 使用自旋鎖,保證數據的原子性
  • 其實也是一種樂觀鎖

樂觀鎖

悲觀鎖

獨享鎖(寫鎖)

共享鎖(讀鎖)

可重入鎖

  • synchronized 就是可重入鎖
    • 同一個線程,在拿到一次鎖以後,能夠繼續調用同一把鎖所同步的代碼。
public synchronized void task(Object arg){
	System.out.print(Thread.currentThread() + "開始執行" + arg);
    if(arg == null){
     	this.task(new Object());   
    }
    System.out.print(Thread.currentThread() + "執行結束" + arg);
}

//main
new Obj().task(null);
複製代碼

同步關鍵字

synchronized

線程封閉

鎖消除

  • 對於某些局部變量的代碼,可能不會出現線程安全問題,那樣鎖就會被消除
public void genStr(){
    //JIT 優化,消除了鎖
    StringBuffer sb = new StringBuffer();
    sb.append("a");
    sb.append("b");
    sb.append("c");
}
複製代碼

鎖粗化

屬於運行時的 JIT 編譯優化git

  • 鎖的範圍會被擴大
  • sych 會鎖到 for 循環外面
public class LockDemo01{
    int count;
  	public void runTest(){
     	 for(int i = 0; i < 10000; i++){
          	synchronized(this){
             	count++;   
            }
         }
    }
}
複製代碼

經過 JitWatch 查看通過 jit 優化後的代碼

www.cnblogs.com/stevenczp/p… www.cnblogs.com/stevenczp/p…github

一、 輸出jit日誌 (windows)在jre/bin/server  放置hsdis動態連接庫 eclise、idea等工具,加上JVM參數 -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.logwindows

二、 工具安裝 下載  github.com/AdoptOpenJD… 解壓 經過maven運行 mvn clean compile exec:java安全

三、 配置jitwatch 頁面選擇 config, 配置要調試的項目src源碼路徑,和class編譯路徑 打開jit.log 點擊start 四、 在分析的結果中,選中指定的類,再選擇右側的具體方法,則彈出jit編譯結果app

同步關鍵字加鎖原理

大致結構

Obj Head 中的 mark word

BitFields 和 State 的對應關係。maven

  • Lock Record Address 存儲 thread stack 中 LockRecord 的地址** >>> 輕量級鎖**
  • Monitor Address 存儲 monitor obj 的地址 >>> 重量級鎖
    • 由於涉及到了 monitor obj,因此能夠認爲是重量級的
  • TheadId 線程在 JVM 中的Id >>> 偏向鎖

HotspotOverview.pdfide

鎖狀態的改變

無鎖 -> 輕量級鎖

CAS!CAS!CAS!CAS!CAS!CAS!CAS!工具

加鎖

解鎖

偏向鎖 -> 輕量級鎖

  • 問題:線程重入是如何斷定的
    • 由於在 Lock Record Address 中記錄了當前持有鎖的線程的棧地址,因此能夠比對出,是否能夠重入

輕量級鎖 -> 重量級鎖

對象頭中鎖的狀態(我的理解)

重量級鎖/輕量級鎖/未鎖定

相關代碼:src.share.vm.runtime.ObjectMonitor

  1. 當 線程-1 請求持有鎖時,若是對象時 01 (unlock),則會得到鎖,並將對象頭中的標誌位置爲 輕量級鎖 (00)
  2. 線程是經過 CAS 自旋的方式去請求鎖的
  3. 當對象狀態位 00 時,有其餘線程請求鎖,線程首先經過 CAS 自旋的方式去嘗試得到鎖,當嘗試達到次數沒有得到時,對象的狀態會變爲 10.
  4. 這裏再也不使用自旋等待的緣由是自旋會消耗大量資源
  5. 之因此是重量級,是由於要操做兩個對象 > 原對象、monitor 對象
  6. 此時對象的 monitor 中,_owner 會變爲 線程-1,新的請求線程會放到 _EntryList 中等待
  7. _EntryList 是爭搶隊列!!!
  8. 線程釋放 _owner 會有兩種方式
  9. 當持有鎖的線程執行完後,會 monitorExit.
  10. 調用對象的 wait 方法後,會進入等待集合 _waiters
  11. 線程要進入 _waiters 隊列,須要 _owner 線程調用 wait 方法,因此須要在同步代碼塊中執行 wait()/notify() 操做
  12. 由於等待集合是 Set,因此 notify 喚醒的時候不肯定喚醒哪一個

偏向鎖

優先級: 未鎖定 >>> 偏向鎖 >>> 輕量級 >>> 重量級

  • 輕量級鎖的標誌位存在 對象頭 的 MarkWord 中,默認是開啓的
  • 具體過程
    1. 線程1 請求持有鎖,會先檢查偏向鎖鎖的標誌位,若是爲 1 打開,則經過 CAS 修改線程信息。
    2. 線程信息中記錄了當前得到偏向鎖的線程的 ThreadId
    3. 若是修改爲功,就拿到了鎖
    4. 若是經過 CAS 修改失敗,且線程狀態是 01 未鎖定,則升級到輕量級鎖被清空標誌位
    5. 偏向鎖釋放,會清空線程信息
  • 爲何要用偏向鎖
    • 由於 JVM 的設計理念認爲,大多數狀況下,並不存在鎖競爭,不須要頻繁修改標誌位,減小了變爲 重量級鎖 的可能性。帶來了性能提高。

參考

open jdk


版權聲明

image.png

  • 本文做者: 留夕
  • 本文連接: www.yuque.com/diamond/ndv…
  • 版權聲明: 本博客全部文章除特別聲明外,均採用 CC BY-SA 4.0 許可協議。轉載請註明出處!
  • 首發日期: 2019-6-27 21:10:45
相關文章
相關標籤/搜索