并發編程-Synchronized

Mark Word

什么是Mark Word

Mark Word是Java對象頭中的一個字段,它是一個32位或64位的字段(取決于系統架構),用于存儲對象的元數據信息。這些信息包括對象的哈希碼、鎖狀態、年齡等。

Mark Word有什么用?

Mark Word的主要用途包括:

  1. 存儲哈希碼:在對象需要計算哈希碼時,可以直接從Mark Word中讀取,避免重復計算。

  2. 管理鎖狀態Mark Word用于存儲對象的鎖狀態,支持多種鎖機制,如偏向鎖、輕量級鎖和重量級鎖。

  3. 垃圾回收Mark Word中的年齡信息用于垃圾回收,幫助垃圾回收器決定何時回收對象。

Mark Word儲存在哪?

下圖為圖示

Mark Word是Java對象頭的一部分,存儲在對象的內存布局中。對象的內存布局通常包括以下部分:

  1. Mark Word:存儲對象的元數據信息。

  2. Klass Word:存儲對象的類信息。

  3. 對象數據:存儲對象的實際數據。

在32位系統中,Mark Word是一個32位的字段;在64位系統中,Mark Word是一個64位的字段。為了節省內存,Java在64位系統中使用了一種稱為“指針壓縮”的技術,將Mark Word壓縮為32位。

結構

1. Normal(正常狀態)

  • 位布局

    • hashcode:25:25位用于存儲對象的哈希碼。

    • age:4:4位用于存儲對象的年齡(用于垃圾回收)。

    • biased_lock:0:1位用于表示是否啟用偏向鎖(0表示未啟用)。

    • 01:2位用于表示鎖的狀態(01表示正常狀態)。

2. Biased(偏向鎖狀態)

  • 位布局

    • thread:23:23位用于存儲偏向鎖的線程ID。

    • epoch:2:2位用于存儲偏向鎖的紀元信息。

    • age:4:4位用于存儲對象的年齡。

    • biased_lock:1:1位用于表示是否啟用偏向鎖(1表示已啟用)。

    • 01:2位用于表示鎖的狀態(01表示偏向鎖狀態)。

3. Lightweight Locked(輕量級鎖狀態)

  • 位布局

    • ptr_to_lock_record:30:30位用于存儲指向鎖記錄的指針。

    • 00:2位用于表示鎖的狀態(00表示輕量級鎖狀態)。

4. Heavyweight Locked(重量級鎖狀態)

  • 位布局

    • ptr_to_heavyweight_monitor:30:30位用于存儲指向重量級監視器的指針。

    • 10:2位用于表示鎖的狀態(10表示重量級鎖狀態)。

5. Marked for GC(標記為垃圾回收狀態)

  • 位布局

    • 11:2位用于表示鎖的狀態(11表示標記為垃圾回收狀態)。

Monitor

Monitor 是什么?

在 Java 中,Monitor(也稱為 對象鎖內置鎖)是 JVM 實現線程同步的核心機制。它用于控制對共享資源的訪問,確保在多線程環境下,對共享資源的訪問是線程安全的。

一個對象會關聯一個monitor

Monitor 的作用

Monitor 的主要作用是:

  • 實現線程同步:通過控制對共享資源的訪問,確保同一時間只有一個線程可以訪問共享資源。

  • 實現鎖機制:Monitor 是 Java 中 synchronized 關鍵字的底層實現

  • 管理線程等待與喚醒:Monitor 內部維護了多個隊列,用于管理等待鎖的線程和等待被通知的線程。

  • JVM 統一管理 Monitor:雖然 Monitor 是對象的一部分,但 JVM 會通過全局的機制來管理所有 Monitor 的生命周期。例如,JVM 會維護一個全局的 Monitor 列表,用于分配和回收 Monitor 對象。

  • Monitor 的分配是線程安全的:JVM 會為每個線程維護一個可用的 Monitor 列表(freeused),當線程需要獲取鎖時,會從這些列表中申請 Monitor 對象。

Monitor 的結構

Monitor 是一個線程私有的數據結構,通常由 JVM 實現(如 HotSpot 虛擬機中的 ObjectMonitor 類)。它包含以下關鍵部分:

字段說明
_owner指向當前持有鎖的線程的唯一標識(如 Thread 對象)
_EntryList等待獲取鎖的線程(阻塞隊列)
_WaitSet等待被通知的線程(等待隊列)
_recursions記錄鎖的重入次數
_count鎖的計數器(用于判斷是否為可重入鎖)

Monitor 的生命周期

  • 創建:當一個對象被 synchronized 加鎖時,JVM 會為該對象創建一個 Monitor 對象。

  • 銷毀:Monitor 是與對象的生命周期一致的,當對象被垃圾回收時,Monitor 也會被銷毀。

  • 統一管理:JVM 會通過全局機制來管理所有 Monitor 的生命周期,包括分配和回收。

總結

問題回答
Monitor 是什么?Monitor 是 Java 中實現線程同步的核心機制,是 synchronized 關鍵字的底層實現。
Monitor 的作用是什么?Monitor 的作用是實現線程同步,確保對共享資源的訪問是線程安全的。
Monitor 的結構是什么?Monitor 包含 _owner_EntryList_WaitSet_recursions 等字段。
Monitor 的存儲位置在哪里?Monitor 的地址存儲在對象頭的 Mark Word 中。
Monitor 是如何被創建和管理的?Monitor 是 JVM 自動創建的,與對象的生命周期一致,由 JVM 統一管理。
Monitor 的作用機制是什么?Monitor 通過 monitorentermonitorexit 實現鎖的獲取和釋放,支持可重入性。
Monitor 的優化機制有哪些?包括偏向鎖、輕量級鎖和重量級鎖等優化機制。

synchronized

是什么

synchronized 實際是用對象鎖保證了臨界區內代碼的原子性,臨界區內的代碼對外是不可分割的,不會被線程切換所打斷。

就是說 假設現在有兩個線程,那么當線程1獲取鎖在執行的時候,當線程1的時間片用完,但是線程1里面的代碼還沒有執行完, 那么線程2就不能拿到鎖,就會進入等待狀態,當線程1執行完臨界區代碼,釋放鎖,線程2就會獲取鎖,繼續執行臨界區代碼。


?

輕量級鎖

Mark work布局

  • ptr_to_lock_record:30:30位用于存儲指向鎖記錄的指針。

  • 00:2位用于表示鎖的狀態(00表示輕量級鎖狀態)。

輕量級鎖是Java中一種用于優化同步操作的鎖機制,其主要目的是在沒有多線程競爭的情況下,減少鎖的開銷。輕量級鎖的使用場景是當多個線程對同一個對象加鎖的時間是錯開的(即沒有競爭)時,可以使用輕量級鎖來優化。這種情況下,線程之間不會發生阻塞,從而提高了性能。

輕量級鎖的加鎖過程

當一個線程進入同步塊時,如果該對象沒有被鎖定(即鎖標志位為“01”狀態),虛擬機會在當前線程的棧幀中創建一個名為鎖記錄(Lock Record)的空間,用于存儲對象頭中的Mark Word的拷貝。然后,虛擬機會嘗試使用CAS操作將對象的Mark Word更新為指向鎖記錄的指針。

如果更新成功,表示當前線程獲得了鎖,并且對象的Mark Word的鎖標志位會被設置為“00”,表示該對象處于輕量級鎖狀態。

輕量級鎖的解鎖過程

當線程退出同步塊時,虛擬機會嘗試使用CAS操作將當前線程的鎖記錄替換回對象頭。如果替換成功,表示沒有競爭發生,同步過程完成;如果替換失敗,說明有其他線程嘗試獲取鎖,此時需要喚醒被阻塞的線程。

加鎖失敗

在輕量級鎖的加鎖過程中,加鎖失敗通常有兩種情況,而鎖重入是其中一種特殊情況。以下是詳細分析:

1. 鎖已經被其他線程持有(競爭發生)

這是最常見的加鎖失敗原因。當一個線程在嘗試獲取輕量級鎖時,發現對象的Mark Word已經被其他線程修改為“00”狀態(表示輕量級鎖狀態),說明該對象已經被其他線程加鎖,或者當前線程已經嘗試過加鎖但失敗。

  • 原因:多個線程對同一個對象進行同步操作,且加鎖時間重疊,導致競爭。

  • 處理方式:輕量級鎖會嘗試升級為重量級鎖,通過操作系統級的互斥量來實現真正的互斥。

2. 鎖重入(Reentrant Lock)

鎖重入是輕量級鎖的一種特殊情況,指的是同一個線程對同一個對象多次加鎖。在輕量級鎖中,每次重入時,鎖記錄中會存儲一個重入計數器,表示該線程對鎖的持有次數。

  • 判斷方式:在加鎖時,如果發現對象的Mark Word指向的是當前線程的棧幀,說明是鎖重入,直接繼續執行同步代碼即可。

  • 處理方式:在鎖重入時,鎖記錄中的displaced_header會被置為NULL,表示這是一個重入的鎖記錄。解鎖時,如果發現displaced_headerNULL,則說明是鎖重入,不需要恢復Mark Word,只需遞減重入計數器即可。

重量級鎖

Mark Work布局

  • ptr_to_heavyweight_monitor:30:30位用于存儲指向重量級監視器的指針。

  • 10:2位用于表示鎖的狀態(10表示重量級鎖狀態)。

在多線程環境下,Java中的鎖機制會根據競爭情況動態調整鎖的狀態,從無鎖到偏向鎖、輕量級鎖,最終到重量級鎖。當多個線程競爭同一個對象鎖時,輕量級鎖可能會膨脹為重量級鎖。以下是詳細的鎖膨脹過程:

初始狀態

  • 輕量級鎖:當一個線程嘗試獲取對象鎖時,如果對象處于無鎖狀態,線程會在自己的棧幀中創建一個鎖記錄(Lock Record),并將對象的Mark Word復制到這個鎖記錄中。然后,線程嘗試通過CAS操作將對象的Mark Word更新為指向該鎖記錄的指針,并將Mark Word的最后兩位設置為00,表示輕量級鎖狀態。

鎖膨脹觸發條件

  • CAS操作失敗:如果在嘗試加輕量級鎖的過程中,CAS操作無法成功,說明有其他線程已經為該對象加了輕量級鎖。此時,當前線程無法通過CAS操作將Mark Word更新為指向自己的鎖記錄,因此CAS操作會失敗。

鎖膨脹過程

  • 申請Monitor鎖:當CAS操作失敗后,線程會進入鎖膨脹流程。首先,線程會為對象申請一個Monitor鎖,并將對象的Mark Word更新為指向這個Monitor對象的地址。同時,Mark Word的最后兩位會被設置為10,表示重量級鎖狀態。

  • 線程阻塞:由于重量級鎖支持阻塞機制,當前線程會被放入Monitor的EntryList中,并進入BLOCKED狀態,等待其他線程釋放鎖。

解鎖過程

  • 解鎖失敗:當持有輕量級鎖的線程(Thread-0)退出同步代碼塊時,它會嘗試通過CAS操作將Mark Word恢復為原來的值(即之前保存在鎖記錄中的值)。然而,由于鎖已經膨脹為重量級鎖,Mark Word的值已經指向Monitor對象,并且最后兩位為10,因此CAS操作會失敗。

  • 重量級解鎖流程:當CAS操作失敗后,線程會進入重量級解鎖流程。具體步驟如下:

    • 根據Mark Word中的Monitor地址找到對應的Monitor對象。

    • 將Monitor對象的Owner字段設置為null,表示沒有線程持有該鎖。

    • 喚醒EntryList中所有被阻塞的線程,讓它們有機會競爭鎖。

自旋優化 自適應自旋鎖

重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前線程自旋成功(即這時候持鎖線程已經退出了同步塊,釋放了鎖),這時當前線程就可以避免阻塞

自旋重試成功的情況

線程 1 (cpu 1 上)對象 Mark線程 2 (cpu 2 上)
-10(重量鎖)-
訪問同步塊,獲取 monitor10(重量鎖)重量鎖指針-
成功(加鎖)10(重量鎖)重量鎖指針-
執行同步塊10(重量鎖)重量鎖指針訪問同步塊,獲取 monitor
執行同步塊10(重量鎖)重量鎖指針自旋重試
執行完畢10(重量鎖)重量鎖指針自旋重試
成功(解鎖)01(無鎖)自旋重試
-10(重量鎖)重量鎖指針成功(加鎖)
-10(重量鎖)重量鎖指針執行同步塊

自旋鎖一般會重試 10 次。這是 Java 中自旋鎖的默認設置,由 JVM 內部參數 _spinFreq 控制,表示線程在嘗試獲取鎖失敗后,最多會自旋(即循環嘗試)10 次。如果在這 10 次嘗試中仍未成功獲取鎖,線程將不再自旋,而是進入阻塞狀態,等待鎖被釋放 。

從 JDK 1.7 開始,自旋鎖啟用,并且自旋次數由 JVM 動態決定,稱為“自適應自旋鎖”。這種機制會根據前一次在同一個鎖上的自旋時間和鎖的持有者狀態來調整自旋次數,從而在不同場景下都能達到最佳性能 。

自旋重試失敗的情況

線程 1 (cpu 1 上)對象 Mark線程 2 (cpu 2 上)
-10(重量鎖)-
訪問同步塊,獲取 monitor10(重量鎖)重量鎖指針-
成功(加鎖)10(重量鎖)重量鎖指針-
執行同步塊10(重量鎖)重量鎖指針訪問同步塊,獲取 monitor
執行同步塊10(重量鎖)重量鎖指針自旋重試
執行同步塊10(重量鎖)重量鎖指針自旋重試
執行同步塊10(重量鎖)重量鎖指針阻塞
.........
  • 在 Java 6 之后自旋鎖是自適應的,比如對象剛剛的一次自旋操作成功過,那么認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能。

  • 自旋會占用 CPU 時間,單核 CPU 自旋就是浪費,多核 CPU 自旋才能發揮優勢。

  • Java 7 之后不能控制是否開啟自旋功能

偏向鎖

Mark Work布局

  • thread:23:23位用于存儲偏向鎖的線程ID。

  • epoch:2:2位用于存儲偏向鎖的紀元信息。

  • age:4:4位用于存儲對象的年齡。

  • biased_lock:1:1位用于表示是否啟用偏向鎖(1表示已啟用)。

  • 01:2位用于表示鎖的狀態(01表示偏向鎖狀態)。

偏向鎖是Java 6中引入的一種鎖優化機制,旨在進一步減少鎖競爭帶來的性能開銷。它在無鎖競爭的情況下,通過將鎖直接偏向于第一個獲取它的線程,從而避免了輕量級鎖中頻繁的CAS(Compare and Swap)操作。

偏向鎖的引入背景

在輕量級鎖的基礎上,JVM發現大多數情況下鎖并不會被多個線程競爭,尤其是同一個線程多次獲取同一把鎖的場景。為了進一步優化這種無競爭情況下的鎖性能,JVM引入了偏向鎖。偏向鎖的核心思想是:將鎖的持有者(線程)直接記錄在對象頭中,后續訪問該鎖的線程無需再進行CAS操作,從而減少不必要的鎖競爭開銷。

偏向鎖的工作原理

第一次加鎖:當第一個線程訪問同步代碼塊時,JVM會通過CAS操作將該線程的ID寫入對象頭的Mark Word中,并將鎖標志位設置為“101”(表示偏向鎖狀態)。

后續訪問:當同一個線程再次訪問該同步代碼塊時,JVM只需檢查對象頭中的線程ID是否與當前線程一致。如果一致,則直接進入同步代碼塊,無需再進行CAS操作。

鎖重入:如果同一個線程對同一對象多次加鎖,JVM會自動處理鎖的重入,但每次重入時仍然只需判斷線程ID是否匹配,無需執行CAS操作。

輕量級鎖和偏向鎖圖示:

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

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

相關文章

【51單片機】5. 矩陣鍵盤與矩陣鍵盤密碼鎖Demo

1. 矩陣鍵盤原理 通過矩陣連接的模式,原本需要16個引腳連接的按鈕只需要8個引腳就能連接好,減少了I/O口的占用。 矩陣按鈕是通過掃描來讀取狀態的。 2. 掃描的概念 輸出掃描示例:數碼管掃描 原理:顯示第1位→顯示第2位→顯示第…

Android Studio jetpack compose折疊日歷日期選擇器【折疊日歷】

今天寫一個日期選擇器,大家根據自己需求改代碼,記得點贊支持,謝謝~ 這是進入的默認狀態 折疊狀態選中本周其他日期狀態 切換上下周狀態 展開日歷狀態 切換上下月狀態 選中狀態 代碼如下: import android.content.C…

馭碼CodeRider 2.0全棧開發實戰指南:從零構建現代化電商平臺

馭碼CodeRider 2.0全棧開發實戰指南:從零構建現代化電商平臺 一、CodeRider 2.0:重新定義全棧智能開發 1.1 革命性升級亮點 #mermaid-svg-AKjytNB4hD95UZtF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AKjyt…

大模型智能體AutoGen面試題及參考答案

目錄 AutoGen 的核心是什么? Agent 在 AutoGen 中承擔什么角色? AutoGen 是如何定義 AssistantAgent、UserProxyAgent 等代理類型的? 什么是 GroupChat(組對話)模式? AutoGen 的 system message 在框架中扮演什么作用? 如何通過 Agent 實現自然語言處理? AutoGen…

深度學習筆記26-天氣預測(Tensorflow)

🍨 本文為🔗365天深度學習訓練營中的學習記錄博客🍖 原作者:K同學啊 一、前期準備 1.數據導入 import numpy as np import pandas as pd import warnings import seaborn as sns import matplotlib.pyplot as plt warnings.filt…

day54 python對抗生成網絡

目錄 一、GAN對抗生成網絡思想 二、實踐過程 1. 數據準備 2. 構建生成器和判別器 3. 訓練過程 4. 生成結果與可視化 三、學習總結 一、GAN對抗生成網絡思想 GAN的核心思想非常有趣且富有對抗性。它由兩部分組成:生成器(Generator)和判…

龍虎榜——20250613

上證指數放量下跌收陰線,個股下跌超4000只,受外圍消息影響情緒總體較差。 深證指數放量下跌,收陰線,6月總體外圍風險較高,轉下跌走勢的概率較大,注意風險。 2025年6月13日龍虎榜行業方向分析 1. 石油石化&…

Linux常用命令加強版替代品

Linux常用命令加強版替代品 還在日復一日地使用 ls、grep、cd 這些“上古”命令嗎?是時候給你的終端來一次大升級了!本文將為你介紹一系列強大、高效且設計現代的Linux命令行工具,它們將徹底改變你的工作流,讓你愛上在終端里操作…

Hadoop 003 — JAVA操作MapReduce入門案例

MapReduce入門案例-分詞統計 文章目錄 MapReduce入門案例-分詞統計1.xml依賴2.編寫MapReduce處理邏輯3.上傳統計文件到HDFS3.配置MapReduce作業并測試4.執行結果 1.xml依賴 <dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-commo…

Python打卡第53天

浙大疏錦行 作業&#xff1a; 對于心臟病數據集&#xff0c;對于病人這個不平衡的樣本用GAN來學習并生成病人樣本&#xff0c;觀察不用GAN和用GAN的F1分數差異。 import pandas as pd import numpy as np import torch import torch.nn as nn import torch.optim as optim from…

力扣-279.完全平方數

題目描述 給你一個整數 n &#xff0c;返回 和為 n 的完全平方數的最少數量 。 完全平方數 是一個整數&#xff0c;其值等于另一個整數的平方&#xff1b;換句話說&#xff0c;其值等于一個整數自乘的積。例如&#xff0c;1、4、9 和 16 都是完全平方數&#xff0c;而 3 和 1…

前端構建工具Webapck、Vite——>前沿字節開源Rspack詳解——2023D2大會

Rspack 以下是針對主流構建工具&#xff08;Webpack、Vite、Rollup、esbuild&#xff09;的核心不足分析&#xff0c;以及 Rspack 如何基于這些痛點進行針對性改進 的深度解析&#xff1a; 一、主流構建工具的不足 1. Webpack&#xff1a;性能與生態的失衡 核心問題 冷啟動慢…

輸入法,開頭輸入這U I V 三個字母會不顯示 任何中文

1. 漢語拼音規則的限制 漢語拼音中不存在以“V”“U”“I”為聲母的情況&#xff1a; 漢語拼音的聲母是輔音&#xff0c;而“V”“U”“I”在漢語拼音中都是元音&#xff08;或韻母的一部分&#xff09;。漢語拼音的聲母系統中沒有“V”“U”“I”作為聲母的音節。例如&#xf…

Linux文件權限詳解:從入門到精通

前言 權限是什么&#xff1f; 本質&#xff1a;無非就是能做和不能做什么。 為什么要有權限呢&#xff1f; 目的&#xff1a;為了控制用戶行為&#xff0c;防止發生錯誤。 1.權限的理解 在學習下面知識之前要先知道的一點是&#xff1a;linux下一切皆文件&#xff0c;對li…

在多云環境透析連接ngx_stream_proxy_protocol_vendor_module

1、模塊定位與價值 多云接入&#xff1a;在同一 Nginx 實例前端接入來自多云平臺的私有鏈路時&#xff0c;能區分 AWS、GCP、Azure 特有的連接 ID。安全審計&#xff1a;自動記錄云平臺側的 Endpoint/VPC ID&#xff0c;有助于聯調和安全事件追蹤。路由分流&#xff1a;基于不…

力扣:基本計算器

基本計算器: 224. 基本計算器 - 力扣&#xff08;LeetCode&#xff09; 本體思路為&#xff0c;將中綴表達式轉為后綴表達式&#xff0c;通過后綴表達式進行運算。 中綴表達式: 我們日常生活中熟知的表達式如12-30 就是一個中綴表達式。 后綴表達式: 150. 逆波蘭表達式求值 - …

《AI日報 · 0613|ChatGPT支持導出、Manus免費開放、GCP全球宕機》

AI 資訊 1?? OpenAI ChatGPT Canvas新增多格式導出功能 OpenAI終于為ChatGPT Canvas推出了用戶期待已久的導出功能。現在,用戶可以將創作內容導出為多種格式:文檔類支持PDF、docx和markdown格式,代碼文件則可直接保存為對應擴展名的源文件(如.py、.js、.sql等)。這一功…

C++中的零拷貝技術

一、C中零拷貝技術的核心概念 零拷貝&#xff08;Zero-copy&#xff09;是一種重要的優化技術&#xff0c;旨在減少數據在內存中的不必要復制&#xff0c;從而提高程序性能、降低內存使用并減少CPU消耗。在C中&#xff0c;零拷貝技術通過多種方式實現&#xff0c;包括引用語義…

RT_Thread內核源碼分析(五)——內存管理@小堆內存管理算法

目錄 1、內存堆控制 1.1 內存堆控制器 1.2 內存塊節點 1.3 內存堆管理 2、內存堆初始化 2.1 初始化接口 2.2 初始化示例 2.3 源碼分析 3、內存堆操作 3.1 內存塊申請 3.1.1 相關接口 3.1.2 原理分析 3.1.3 示例分析 3.1.4 代碼分析 3.2 內存塊伸縮 3.2.1 相關…

MyBatis-Plus 混合使用 XML 和注解

mybatisplus代碼生成器&#xff1a; 版本匹配是個比較麻煩的問題&#xff0c;這是我的配置&#xff1a; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>…