11.偏向鎖原理及其實戰

文章目錄

  • 偏向鎖原理及其實戰
    • 1.偏向鎖原理
    • 2.偏向鎖案例代碼演示
      • 2.1.偏向鎖案例代碼
        • 2.2.1.無鎖情況下狀態
        • 2.1.2.偏向鎖狀態
        • 2.1.3.釋放鎖后的狀態
      • 2.2.偏向鎖的膨脹和撤銷
        • 2.2.1.偏向鎖撤銷的條件
        • 2.2.2.偏向鎖的撤銷
      • 2.2.3.偏向鎖的膨脹
    • 2.3.全局安全點原理和偏向鎖撤銷性能問題
      • 2.3.1.全局安全點介紹
      • 2.3.1 偏向鎖撤銷性能問題

偏向鎖原理及其實戰

偏向鎖主要用來解決無競爭下鎖性能問題,在實際場景中,如果一個同步代碼塊(方法)沒有多個線程競爭,而且總是有一個線程多次重入獲取鎖,并且線程每次還有阻塞線程,更改線程狀態為運行狀態等操作,那么此時相對于CPU是一種資源浪費,為了解決這個問題,就引入了偏向鎖

1.偏向鎖原理

偏向鎖原理:

  1. 當一個線程獲取了一個對象的鎖時,JVM會將該對象的鎖標記位設置為01,偏向位設置為1,表示該對象進入了偏向鎖狀態。

  2. JVM會使用CAS(Compare and Swap)操作將獲取鎖的線程ID記錄在對象的Mark Word中,如果CAS操作失敗,說明有其他線程競爭獲取鎖。

  3. 當其他線程嘗試獲取該對象的鎖時,JVM會檢查對象的鎖標記位和偏向位。如果鎖標記位為01且偏向位為1,表示對象處于偏向鎖狀態,并且有線程ID記錄在Mark Word中。

  4. 如果嘗試獲取偏向鎖的線程ID與Mark Word中記錄的線程ID相同,說明該線程仍然是獲取鎖的線程,可以直接進入同步代碼塊,無需使用CAS操作。

  5. 如果嘗試獲取偏向鎖的線程ID與Mark Word中記錄的線程ID不同,說明有其他線程競爭鎖,此時偏向鎖會自動升級為輕量級鎖狀態。

偏向鎖的引入主要是為了優化無競爭情況下的鎖性能。在無競爭的情況下,偏向鎖可以避免多余的同步操作,從而提高程序的性能。然而,由于偏向鎖需要記錄線程ID并使用CAS操作,會引入一定的額外開銷。因此,JVM會延遲啟用偏向鎖,只對一定時間后創建的對象進行偏向鎖的開啟。

雖然JVM默認開啟偏向鎖,但是延時 4s 開啟,程序創建對象的時候并不會開啟偏向鎖, 4s后創建的對象才會開啟偏向鎖。

需要注意的是,偏向鎖并不適用于具有競爭的情況,當存在多個線程競爭同一個對象的鎖時,偏向鎖會自動升級為輕量級鎖或重量級鎖,以保證線程的互斥訪問和數據一致性。

在這里插入圖片描述

2.偏向鎖案例代碼演示

2.1.偏向鎖案例代碼

注意 新版的JDK可能默認是禁用偏向鎖的 ,所以需要再JVM啟動參數上添加 開啟偏向鎖的相關代碼

-XX:+UseBiasedLocking

在這里插入圖片描述

/*** 偏向鎖*/
public class BiasedLockDemo {private static final Logger log = LoggerFactory.getLogger(BiasedLockDemo.class);@Test@DisplayName("偏向鎖測試")public void test() {log.error("JVM詳細信息: {}", VM.current().details());// 休眠5sSleepUtil.sleepMillis(5000);// 創建對象MyObjectLock myObjectLock = new MyObjectLock();log.error("無鎖情況下,lock的狀態!");myObjectLock.printLockStatus();SleepUtil.sleepMillis(5000);CountDownLatch latch = new CountDownLatch(1);Runnable runnable = ()->{// 模擬同一個線程多次進入同步代碼塊for (int i = 0; i < 1000; i++) {synchronized (myObjectLock){myObjectLock.increase();if (i == 1000 / 2){log.error("占有鎖情況下!lock狀態!");myObjectLock.printLockStatus();}}SleepUtil.sleepMillis(10);}latch.countDown();};new Thread(runnable,"biased-thread").start();// 等待所有枷鎖線程執行完畢try {latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}// 等待5s 查看鎖的狀態SleepUtil.sleepMillis(5000);log.error("釋放鎖后鎖的狀態!");myObjectLock.printLockStatus();}
}
class MyObjectLock{private static final Logger log = LoggerFactory.getLogger(MyObjectLock.class);private int count = 0;/*** 打印當前對象的一個狀態*/public void printLockStatus(){log.error(ClassLayout.parseInstance(this).toPrintable());}/*** 將當前共享變量自增*/public void increase(){this.count++;}
}

這里 我們分為三個部分進行說明

2.2.1.無鎖情況下狀態

在這里插入圖片描述

  • 對象頭部分的第一個4字節(OFFSET 0)表示對象的標記字(Mark Word)。在這個結果中,標記字的十六進制表示為05 00 00 00,對應的二進制為00000101 00000000 00000000 00000000。其中,第一個位為偏向鎖標志位,為1表示啟用了偏向鎖。在這個結果中,偏向鎖標志位為1,說明該對象啟用了偏向鎖。
  • 其中 d8 f9 09 01 (11011000 11111001 00001001 00000001) (17431000)為其Class Pointer(類對象指針)
  • 對象頭部分的第二個4字節(OFFSET 4)和第三個4字節(OFFSET 8)也是對象頭信息。在這個結果中,這兩個字節的值都為0,沒有包含其他重要的標志位信息。
  • 接下來的4字節(OFFSET 12)表示MyObjectLock對象中的一個整型字段count。在這個結果中,count字段的值為0。
  • 最后,結果顯示了對象的實例大小為16字節,并且沒有內部或外部的空間損失。
2.1.2.偏向鎖狀態

在輸出MyObjectLock實例結構后,等待5s,然后啟動一個線程占用偏向鎖,因為輸出的內容比較多,所以這里選擇了到中間值的時候進行輸出結構。

偏向鎖狀態

  • 對象頭部分的第一個4字節(OFFSET 0)表示對象的標記字(Mark Word)。在這個結果中,標記字的十六進制表示為05 80 e6 c1,對應的二進制為00000101 10000000 11100110 11000001。其中,第一個位為偏向鎖標志位,為1表示啟用了偏向鎖。在這個結果中,偏向鎖標志位為1,說明該對象啟用了偏向鎖。
  • 對象頭部分的第二個4字節(OFFSET 4)和第三個4字節(OFFSET 8)也是對象頭信息。在這個結果中,這兩個字節的值分別為c0 02 00 00d8 f9 09 01
  • 接下來的4字節(OFFSET 12)表示MyObjectLock對象中的一個整型字段count。在這個結果中,count字段的值為501。
  • 最后,結果顯示了對象的實例大小為16字節,并且沒有內部或外部的空間損失。
2.1.3.釋放鎖后的狀態

在這里插入圖片描述

  • 對象頭部分的第一個4字節(OFFSET 0)表示對象的標記字(Mark Word)。在這個結果中,標記字的十六進制表示為05 80 e6 c1,對應的二進制為00000101 10000000 11100110 11000001。其中,第一個位為偏向鎖標志位,為1表示啟用了偏向鎖。在這個結果中,偏向鎖標志位為1,說明該對象啟用了偏向鎖。
  • 對象頭部分的第二個4字節(OFFSET 4)和第三個4字節(OFFSET 8)也是對象頭信息。在這個結果中,這兩個字節的值分別為c0 02 00 00d8 f9 09 01
  • 接下來的4字節(OFFSET 12)表示MyObjectLock對象中的一個整型字段count。在這個結果中,count字段的值為1000。
  • 最后,結果顯示了對象的實例大小為16字節,并且沒有內部或外部的空間損失。

2.2.偏向鎖的膨脹和撤銷

假如有多個線程來競爭偏向鎖,此對象鎖就不會有所偏向了,其他線程發現偏向鎖并不是偏向自己,就說明存在了競爭,會嘗試撤銷偏向鎖,然后膨脹到輕量級鎖。

2.2.1.偏向鎖撤銷的條件
  • 多個線程存在競爭
  • 調用偏向鎖對象的obj的obj.hashCode或者System.indentityHsahCode()方法計算對象的hash碼,偏向鎖將會被撤銷。
    • 因為一個對象的哈希碼只會生成一次,并且保存在Mark Word中,偏向鎖的Mark Word 已經保存了線程ID,沒有其他地方在保存哈希碼了,所以只能撤銷偏向鎖
    • 輕量級鎖會在棧幀的Lock Record(鎖記錄)中記錄哈希碼
    • 重量級鎖會在監視器中記錄哈希碼
2.2.2.偏向鎖的撤銷

偏向鎖的撤銷的開銷花費是挺大的,其大概過程如下

  1. JVM需要等待一個全局安全點,當JVM到達全局安全點后,所有的用戶線程都是暫停的,當前持有偏向鎖的用戶線程也是暫停的。
  2. 遍歷線程的棧幀,檢查是否存在鎖記錄,如果存在鎖記錄,那么就清空鎖記錄,使其變成無鎖的狀態,并修復鎖記錄指向的線程ID,清除其線程ID
  3. 將當前鎖升級(或碰撞)成輕量級鎖,少數場景直接升級為重量級鎖
  4. 喚醒當前線程

2.2.3.偏向鎖的膨脹

如果偏向鎖被占據,那么第二個線程爭搶這個對象,因為偏向鎖不會主動釋放,所以第二個線程可以看到內置鎖的偏向狀態,這時JVM會檢查原來持有該偏向鎖線程是否存活,如果掛了,那么將該對象變為無鎖狀態,重新偏向,如果沒掛,就發生競爭,進行膨脹。

  1. 當一個線程嘗試獲取一個偏向鎖時,如果該鎖的標記字(Mark Word)指向的線程ID與當前線程ID不一致,表示存在競爭,此時偏向鎖需要膨脹。
  2. JVM會將對象的標記字從偏向鎖狀態修改為輕量級鎖狀態。這個過程稱為鎖的膨脹。
  3. 膨脹為輕量級鎖的過程包括以下步驟:
    • JVM會嘗試使用CAS(Compare and Swap)操作將對象的標記字修改為指向鎖記錄的指針,同時更新鎖記錄中的線程ID為當前線程ID。
    • 如果CAS操作成功,表示膨脹為輕量級鎖成功。
    • 如果CAS操作失敗,說明存在競爭,此時會進一步膨脹為重量級鎖。
  4. 膨脹為重量級鎖的過程包括以下步驟:
    • JVM會在堆中分配一個監視器(Monitor)對象,用于管理鎖的狀態。
    • 將對象的標記字指向該監視器對象,并將鎖記錄中的線程ID修改為0。
    • 此時,鎖的狀態變為重量級鎖。

2.3.全局安全點原理和偏向鎖撤銷性能問題

2.3.1.全局安全點介紹

在Java虛擬機(JVM)中,全局安全點(Global Safepoint)是一個重要的概念,用于確保在某個特定的時間點,所有的線程都處于安全狀態,可以被安全地中斷。全局安全點的引入是為了支持一些關鍵操作,例如線程停止、垃圾回收等。

實現全局安全點的核心思想是在代碼的特定位置插入安全點檢查。安全點檢查是一段特殊的機器碼指令,用于判斷當前線程是否到達安全點。當一個線程到達安全點時,它會被停止,并且進一步的操作需要等待其他線程也到達安全點。只有當所有線程都到達安全點時,JVM才能執行需要線程處于安全狀態的操作。

全局安全點的引入是為了解決并發線程訪問共享數據的一致性問題。在偏向鎖撤銷的過程中,JVM需要等待全局安全點的到來,以確保所有的線程都被暫停,包括持有偏向鎖的線程。只有當所有線程都暫停時,JVM才能安全地撤銷偏向鎖,并進行相應的操作。

全局安全點的實現機制主要包括以下幾個方面:

  1. 安全點的選定:
    JVM會選擇一些合適的位置作為安全點,通常是在方法調用、循環跳轉等代碼塊的末尾。這些位置被認為是安全點,因為在這些位置上線程處于安全狀態,可以被安全地中斷。安全點的選定通常是通過靜態分析和動態檢測相結合的方式來確定的。
  2. 安全點的設置:
    JVM會在代碼中插入安全點檢查的指令,用于檢查當前線程是否到達安全點。這些指令通常是無操作(NOP)指令或輕量級的計算指令,不會對程序的語義產生影響。當線程執行到安全點檢查指令時,會檢查當前線程是否到達安全點,如果沒有到達則會等待其他線程也到達安全點。
  3. 安全點的等待:
    當一個線程到達安全點時,它會被停止,并且進一步的操作需要等待其他線程也到達安全點。只有當所有線程都到達安全點時,JVM才能執行需要線程處于安全狀態的操作。線程的等待是通過自旋等待或掛起等待的方式來實現的。
  4. 安全點的恢復:
    當所有線程都到達安全點后,JVM可以執行需要線程處于安全狀態的操作。完成操作后,JVM會恢復線程的執行,使其繼續執行下去。恢復線程的執行可以通過喚醒等待線程或者設置標記等方式來實現。

2.3.1 偏向鎖撤銷性能問題

偏向鎖撤銷的過程相對于偏向鎖的獲取和釋放而言,具有較高的開銷。這是因為偏向鎖撤銷需要等待全局安全點,并進行一系列的操作來撤銷偏向鎖。這些額外的操作會增加系統開銷,影響性能。

偏向鎖撤銷的性能問題主要體現在以下幾個方面:

  1. 全局安全點的等待時間:在偏向鎖撤銷過程中,JVM需要等待全局安全點的到來。這會導致線程在等待期間無法執行其他有用的工作,從而造成性能損失。
  2. 線程棧幀的遍歷和修改:在偏向鎖撤銷過程中,JVM需要遍歷線程的棧幀,檢查是否存在鎖記錄,并對鎖記錄進行修改。這涉及到線程狀態的切換和尋址操作,會增加額外的開銷。
  3. 鎖的狀態轉換:偏向鎖撤銷后,鎖需要進行狀態轉換,通常是升級為輕量級鎖或重量級鎖。這種狀態轉換涉及到鎖的標記字和鎖記錄的修改,可能需要進行CAS(Compare and Swap)操作,增加了額外的開銷。

為了減少偏向鎖撤銷的性能開銷,可以采取一些優化措施,例如調整全局安全點的觸發頻率、優化線程棧幀的遍歷算法,以及采用延遲偏向鎖撤銷等策略。這些優化措施可以提高偏向鎖的性能并減少性能損失。然而,需要注意的是,在特定的應用場景下,偏向鎖的撤銷可能仍然會帶來一定的性能開銷。因此,在設計和使用偏向鎖時需要綜合考慮性能與應用需求之間的平衡。

對于高并發應用來說,一般建議關閉偏向鎖(JDK9之后,偏向鎖默認是關閉的)

-XX:-UseBiasedLocking 

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10740.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10740.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10740.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

面試題-實例

1.什么是線程池&#xff1f; 線程池就是事先將創建好的線程進行整合&#xff0c;當需要使用的時候&#xff0c;直接拿出來進行使用&#xff0c;不用現去創建&#xff0c;可以節約開辟的時間&#xff0c;提高效率。 2.線程池有哪些種類&#xff1f; 在java.util.concurrent.Ex…

EPAI手繪建模APP工程圖頂部工具欄

7、工程圖 圖 302 工程圖 工程圖包括頂部常用工具欄、右側工程圖工具欄、左側模型列表欄、中間的工程圖。 (1) 常用工具欄 ① 刪除&#xff0c;選中場景中工程圖元素后&#xff0c;刪除。可以選擇多個工程圖元素同時刪除。 ② 設置&#xff0c;打開工程圖設置頁面&#xff0…

Java基礎(29)表達式語言(EL)的隱式對象及其作用

表達式語言&#xff08;Expression Language&#xff0c;簡稱EL&#xff09;是JSP 2.0及以上版本中引入的一種簡化頁面代碼編寫的語言&#xff0c;它提供了一種在JSP頁面中更容易使用JavaBean屬性及集合的方式。EL主要用于簡化對Java代碼的調用&#xff0c;特別是從后端到前端的…

2024 年最新本地、云服務器安裝部署 miniconda 環境詳細教程(更新中)

Anaconda 概述 Anaconda 是專門為了方便使用 Python 進行數據科學研究而建立的一組軟件包&#xff0c;涵蓋了數據科學領域常見的 Python 庫&#xff0c;并且自帶了專門用來解決軟件環境依賴問題的 conda 包管理系統。主要是提供了包管理與環境管理的功能&#xff0c;可以很方便…

picoCTF-Web Exploitation-More SQLi

Description Can you find the flag on this website. Additional details will be available after launching your challenge instance. Hints SQLiLite 先隨便輸入個賬號密碼登錄一下&#xff0c;得到查詢SQL&#xff0c;接下來應該對SQL進行某些攻擊來繞過密碼登錄成功 -- …

微信小程序踩坑,skyline模式下,簡易雙向綁定無效

工具版本 基礎庫版本 Skline模式 頁面json設置 問題描述 skyline模式下,textarea,input標簽設置簡易雙向綁定 model:value是無效的,關閉skyline模式就正常使用了 截圖展示 這里只展示了textarea標簽,input標簽的簡易雙向綁定也是無效的 總結 我在文檔里面是沒找到skyline里面不…

Android OpenMAX(八)如何學習OMXNodeInstance

前面一篇文章中我們看到media.codec service創建OMX組件后會把組件傳遞給一個OMXNodeInstance對象,并且把OMXNodeInstance對象返回到Framework層,Framework通過調用OMXNodeInstance的API來操作OMX組件。從這一篇開始,我們一起學習OMXNodeInstance,在這里我要強調一下,接下…

動態規劃----股票買賣問題(詳解)

目錄 一.買賣股票的最佳時機&#xff1a; 二.買賣股票的最佳時機含冷凍期&#xff1a; 三.買賣股票的最佳時期含?續費&#xff1a; 四.買賣股票的最佳時機III: 五.買賣股票的最佳時機IV: 買賣股票的最佳時機問題介紹&#xff1a;動態規劃買賣股票的最佳時機是一個經典的…

windows使用Docker-Desktop部署lobe-chat

文章目錄 window安裝docker-desktop下載和啟動lobe-chatAI大語言模型的選擇lobe-chat設置大模型連接 window安裝docker-desktop docker-desktop下載地址 正常安裝應用&#xff0c;然后啟動應用&#xff0c;注意啟動docker引擎 打開右上角的設置&#xff0c;進入Docker Engine設…

算法學習系列(六十):區間DP

目錄 引言區間合并模板一、石子合并二、環形石子合并三、能量項鏈 引言 關于這個區間 D P DP DP &#xff0c;其實是有套路和模板的&#xff0c;題型的話也是變化不多&#xff0c;感覺就那幾種&#xff0c;只不過有些題會用到高精度或者是要記錄方案&#xff0c;所以整體來說…

Unity編輯器如何多開同一個項目?

在聯網游戲的開發過程中&#xff0c;多開客戶端進行聯調是再常見不過的需求。但是Unity并不支持編輯器多開同一個項目&#xff0c;每次都得項目打個包(耗時2分鐘以上)&#xff0c;然后編輯器開一個進程&#xff0c;exe 再開一個&#xff0c;真的有夠XX的。o(╥﹏╥)o沒錯&#…

Hive 與 SQL 標準和主流 SQL DB 的語法區別

文章目錄 1.Hive 簡介2.Hive 與 SQL 標準和主流 SQL DB 的語法區別參考文獻 1.Hive 簡介 Hive是一種基于Hadoop的數據倉庫軟件&#xff0c;可以將結構化數據文件映射為一張數據庫表&#xff0c;并提供了類SQL查詢接口&#xff0c;使得用戶可以使用SQL類語言來查詢數據。Hive可…

7-117 死亡隧道

小毛驢要回家了,憑借著剛從老毛驢處學到的閃爍魔法,小毛驢信心滿滿地出發了。這一次它來到了另一條死亡隧道口,但是,小毛驢不知道死亡威脅隨時存在,因為它所打算穿過的這條死亡隧道即將于T秒時間后坍塌。 已知小毛驢行走的速度是每秒17米,而小毛驢擁有的閃爍法術可以使它…

返回類型后置,一個用途是為了邏輯上的體現?

大家一般都是先關心參數&#xff0c;然后最后再看返回的是什么類型。 在這里把返回類型后置&#xff0c;可能就是一種邏輯上的體現吧 fmt的一個函數。 \fmt\core.h 這個函數的意義&#xff0c;應該就是用變長參數初始化成一個format_arg_store類型的變量&#xff0c;并返回。…

Rust學習筆記(上)

前言 筆記的內容主要參考與《Rust 程序設計語言》&#xff0c;一些也參考了《通過例子學 Rust》和《Rust語言圣經》。 Rust學習筆記分為上中下&#xff0c;其它兩個地址在Rust學習筆記&#xff08;中&#xff09;和Rust學習筆記&#xff08;下&#xff09;。 編譯與運行 Ru…

成功解決No module named ‘huggingface_hub.inference._text_generation‘

成功解決No module named huggingface_hub.inference._text_generation 目錄 解決問題 解決思路 解決方法 解決問題 No module named huggingface_hub.inferen

python使用yaml文件以及元組樣式字符串使用eval的類型轉換

編程中&#xff0c;對于可變內容&#xff0c;最好是將其放入配置文件中&#xff0c;經過這段時間的學習&#xff0c;感覺使用yaml文件很方便。我的環境&#xff1a;win10&#xff0c;python3.8.10。 python使用yaml文件&#xff0c;首先要安裝庫。 pip38 install pyyaml 安裝…

AWTK 開源串口屏開發(18) - 用 C 語言自定義命令

AWTK-HMI 內置了不少模型&#xff0c;利用這些模型開發應用程序&#xff0c;不需要編寫代碼即可實現常見的應用。但是&#xff0c;有時候我們需要自定義一些命令&#xff0c;以實現一些特殊的功能。 本文檔介紹如何使用 C 語言自定義命令。 1. 實現 hmi_model_cmd_t 接口 1.1…

實現二叉樹的基本操作

博主主頁: 碼農派大星. 關注博主帶你了解更多數據結構知識 1我們先來模擬創建一個二叉樹 public class TestBinaryTreee {static class TreeNode{public char val;public TreeNode left;public TreeNode right;public TreeNode(char val) {this.val val;}}public TreeNode …

交叉編譯u-boot,qemu啟動測試

交叉編譯u-boot 1 配置交叉編譯工具鏈&#xff1a; 下載地址 https://releases.linaro.org/components/toolchain/binaries/ ### CROSS-COMPILE export AARCH64_LINUX_GNU_TOOLS/media/wmx/cross_compile_tools/aarch64-linux-gun/gcc-x86_64_aarch64-linux-gnu/bin export …