共享模型之管程(悲觀鎖)

共享模型之管程(悲觀鎖)


文章目錄

  • 共享模型之管程(悲觀鎖)
  • 一、常見線程安全的類
  • 二、對象頭
  • 三、Monitor(監視器 / 管程)
  • 四、偏向鎖
    • 偏向鎖的實現原理
    • 撤銷偏向鎖
  • 五、輕量級鎖
    • 輕量級鎖的釋放
  • 六、重量級鎖
  • 七、鎖的升級流程
  • 八、sleep / wait / park
    • sleep
    • wait
    • park
  • 九、多把鎖相關
  • 十、ReentrantLock


一、常見線程安全的類

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的類

他們的每個方法是原子的,但多個方法的組合不是原子的


二、對象頭

  • 普通對象頭
    在這里插入圖片描述
    Mark Word 用來存儲對象的 hashCode 或者鎖信息等。
    Klass Word 存儲到對象類型數據的指針

  • 數組對象頭
    在這里插入圖片描述
    Array length 存儲了數組的長度

  • 其中32位 Mark Word 的結構為
    在這里插入圖片描述

  • 其中64位 Mark Word 的結構為
    在這里插入圖片描述
    從上到下對應的是無鎖、偏向鎖、輕量級鎖、重量級鎖以及GC標志。

可以看到,當對象狀態為偏向鎖時,Mark Word 存儲的是偏向的線程 ID;
當狀態為輕量級鎖時,Mark Word 存儲的是指向線程棧中 Lock Record 的指針;
當狀態為重量級鎖時,Mark Word 為指向堆中的 monitor(監視器)對象的指針。


三、Monitor(監視器 / 管程)

在 Java 中,監視器(monitor)是一種同步工具,用于保護共享數據,避免多線程并發訪問導致數據不一致。在 Java 中,每個對象都有一個內置的監視器。

監視器包括兩個重要部分,一個是鎖,一個是等待/通知機制,后者是通過 Object 類中的wait(), notify(), notifyAll()等方法實現的。
在這里插入圖片描述
剛開始 Monitor 中 Owner 為 null,當 Thread-2 執行 synchronized(obj) 就會將 Monitor 的所有者 Owner 置為 Thread-2,Monitor中只能有一個 Owner。

在 Thread-2 上鎖的過程中,如果 Thread-3,Thread-4,Thread-5 也來執行 synchronized(obj),就會進入EntryList BLOCKED,Thread-2 執行完同步代碼塊的內容,然后喚醒 EntryList 中等待的線程來競爭鎖,競爭的時是非公平的。

Owner 線程發現條件不滿足,調用 wait 方法,即可進入 WaitSet 變為 WAITING 狀態,BLOCKED 和 WAITING 的線程都處于阻塞狀態,不占用 CPU 時間片,BLOCKED 線程會在 Owner 線程釋放鎖時喚醒,WAITING 線程會在 Owner 線程調用 notify 或 notifyAll 時喚醒,但喚醒后并不意味者立刻獲得鎖,仍需進入EntryList 重新競爭。


四、偏向鎖

Hotspot 的作者經過以往的研究發現大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,于是引入了偏向鎖。

偏向鎖會偏向于第一個訪問鎖的線程,如果在接下來的運行過程中,該鎖沒有被其他的線程訪問,則持有偏向鎖的線程將永遠不需要觸發同步。也就是說,偏向鎖在資源無競爭情況下消除了同步語句,連 CAS(后面會細講,戳鏈接直達) 操作都不做了,著極大地提高了程序的運行性能。

大白話就是對鎖設置個變量,如果發現為 true,代表資源無競爭,則無需再走各種加鎖/解鎖流程。如果為 false,代表存在其他線程競爭資源,那么就會走后面的流程。

偏向鎖的實現原理

一個線程在第一次進入同步塊時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程 ID。當下次該線程進入這個同步塊時,會去檢查鎖的 Mark Word 里面是不是放的自己的線程 ID。如果是,表明該線程已經獲得了鎖,以后該線程在進入和退出同步塊時不需要花費 CAS 操作來加鎖和解鎖(對比來說輕量級鎖每次都需要生成鎖記錄,然后用鎖記錄替換 markword );如果不是,就代表有另一個線程來競爭這個偏向鎖。這個時候會嘗試使用 CAS 來替換 Mark Word 里面的線程 ID 為新線程的 ID,這個時候要分兩種情況:

成功,表示之前的線程不存在了, Mark Word 里面的線程 ID 為新線程的 ID,鎖不會升級,仍然為偏向鎖;

失敗,表示之前的線程仍然存在,那么暫停之前的線程,設置偏向鎖標識為 0,并設置鎖標志位為 00,升級為輕量級鎖,會按照輕量級鎖的方式進行競爭鎖。

CAS 是比較并設置的意思,用于在硬件層面上提供原子性操作。在 在某些處理器架構(如x86)中,比較并交換通過指令 CMPXCHG 實現((Compare and Exchange),一種原子指令),通過比較是否和給定的數值一致,如果一致則修改,不一致則不修改。
在這里插入圖片描述
圖中涉及到了 lock record 指針指向當前堆棧中的最近一個 lock record,是輕量級鎖按照先來先服務的模式進行了輕量級鎖的加鎖。

撤銷偏向鎖

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。

偏向鎖升級成輕量級鎖時,會暫停擁有偏向鎖的線程,重置偏向鎖標識,這個過程看起來容易,實則開銷還是很大的,大概的過程如下:

在一個安全點(在這個時間點上沒有字節碼正在執行)停止擁有鎖的線程。
遍歷線程棧,如果存在鎖記錄的話,需要修復鎖記錄和 Mark Word,使其變成無鎖狀態。
喚醒被停止的線程,將當前鎖升級成輕量級鎖。
所以,如果應用程序里所有的鎖通常處于競爭狀態,那么偏向鎖就會是一種累贅,對于這種情況,我們可以一開始就把偏向鎖這個默認功能給關閉。

調用對象的 hashCode 函數時會生成 hashCode,但對于偏向鎖 Mark Word 里記錄的是線程 id,沒地方放哈希值,所以會撤銷偏向鎖。輕量級鎖在鎖記錄里記錄 hashCode,重量級鎖會在 Monitor 中記錄 hashCode。

調用 wait() 或 notify() 方法會觸發偏向鎖的撤銷,并升級為重量級鎖。這是因為 wait() 和 notify() 引入了線程間的競爭和同步機制,而偏向鎖無法應對這種場景。

在這里插入圖片描述


五、輕量級鎖

多個線程在不同時段獲取同一把鎖,即不存在鎖競爭的情況,也就沒有線程阻塞。針對這種情況,JVM 采用輕量級鎖來避免線程的阻塞與喚醒。

JVM 會為每個線程在當前線程的棧幀中創建用于存儲鎖記錄的空間,我們稱為 Displaced Mark Word。如果一個線程獲得鎖的時候發現是輕量級鎖,會把鎖的 Mark Word 復制到自己的 Displaced Mark Word 里面。

然后線程嘗試用 CAS 將鎖的 Mark Word 替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示 Mark Word 已經被替換成了其他線程的鎖記錄,說明在與其它線程競爭鎖,當前線程就嘗試使用自旋來獲取鎖。

自旋:不斷嘗試去獲取鎖,一般用循環來實現。

自旋是需要消耗 CPU 的,如果一直獲取不到鎖的話,那該線程就一直處在自旋狀態,白白浪費 CPU 資源。解決這個問題最簡單的辦法就是指定自旋的次數,例如讓其循環 10 次,如果還沒獲取到鎖就進入阻塞狀態。

但是 JDK 采用了更聰明的方式——適應性自旋,簡單來說就是線程如果自旋成功了,則下次自旋的次數會更多,如果自旋失敗了,則自旋的次數就會減少。

自旋也不是一直進行下去的,如果自旋到一定程度(和 JVM、操作系統相關),依然沒有獲取到鎖,稱為自旋失敗,那么這個線程會阻塞。同時這個鎖就會升級成重量級鎖。

輕量級鎖的釋放

在釋放鎖時,當前線程會使用 CAS 操作將 Displaced Mark Word 的內容復制回鎖的 Mark Word 里面。如果沒有發生競爭,那么這個復制的操作會成功。如果有其他線程因為自旋多次導致輕量級鎖升級成了重量級鎖,那么 CAS 操作會失敗,此時會釋放鎖并喚醒被阻塞的線程。

在這里插入圖片描述


六、重量級鎖

重量級鎖依賴于操作系統的互斥鎖(mutex,用于保證任何給定時間內,只有一個線程可以執行某一段特定的代碼段) 實現,而操作系統中線程間狀態的轉換需要相對較長的時間,所以重量級鎖效率很低,但被阻塞的線程不會消耗 CPU。

每一個對象都可以當做一個鎖,當多個線程同時請求某個對象鎖時,對象鎖會設置幾種狀態用來區分請求的線程:

Contention List:所有請求鎖的線程將被首先放置到該競爭隊列
Entry List:Contention List 中那些有資格成為候選人的線程被移到 Entry List
Wait Set:那些調用 wait 方法被阻塞的線程被放置到 Wait Set
OnDeck:任何時刻最多只能有一個線程正在競爭鎖,該線程稱為 OnDeck
Owner:獲得鎖的線程稱為 Owner
!Owner:釋放鎖的線程

當一個線程嘗試獲得鎖時,如果該鎖已經被占用,則會將該線程封裝成一個ObjectWaiter對象插入到 Contention List 隊列的隊首,然后調用park 方法掛起當前線程。

當線程釋放鎖時,會從 Contention List 或 EntryList 中挑選一個線程喚醒,被選中的線程叫做Heir presumptive即假定繼承人,假定繼承人被喚醒后會嘗試獲得鎖,但synchronized是非公平的,所以假定繼承人不一定能獲得鎖。

這是因為對于重量級鎖,如果線程嘗試獲取鎖失敗,它會直接進入阻塞狀態,等待操作系統的調度。

如果線程獲得鎖后調用Object.wait方法,則會將線程加入到 WaitSet 中,當被Object.notify喚醒后,會將線程從 WaitSet 移動到 Contention List 或 EntryList 中去。需要注意的是,當調用一個鎖對象的wait或notify方法時,如當前鎖的狀態是偏向鎖或輕量級鎖則會先膨脹成重量級鎖。


七、鎖的升級流程

每一個線程在準備獲取共享資源時: 第一步,檢查 MarkWord 里面是不是放的自己的 ThreadId ,如果是,表示當前線程是處于 “偏向鎖” 。

第二步,如果 MarkWord 不是自己的 ThreadId,鎖升級,這時候,用 CAS 來執行切換,新的線程根據 MarkWord 里面現有的 ThreadId,通知之前線程暫停,之前線程將 Markword 的內容置為空。

第三步,兩個線程都把鎖對象的 HashCode 復制到自己新建的用于存儲鎖的記錄空間,接著開始通過 CAS 操作, 把鎖對象的 Markword 的內容修改為自己新建的記錄空間的地址的方式競爭 MarkWord。

第四步,第三步中成功執行 CAS 的獲得資源,失敗的則進入自旋 。

第五步,自旋的線程在自旋過程中,成功獲得資源(即之前獲的資源的線程執行完成并釋放了共享資源),則整個狀態依然處于 輕量級鎖的狀態,如果自旋失敗 。

第六步,進入重量級鎖的狀態,這個時候,自旋的線程進行阻塞,等待之前線程執行完成并喚醒自己。


八、sleep / wait / park

sleep

sleep 是 Thread 類的靜態方法,它的作用是讓當前線程暫停執行一段指定的時間(毫秒或納秒),到時間后自動恢復,無需外部干預,但暫停期間不會釋放持有的鎖

wait

wait 是Object類的實例方法,它的作用是讓當前線程暫停執行,進入waitSet等待,并釋放持有的鎖,直到其他線程調用 notify() 或 notifyAll() 喚醒它。
與sleep不同的是它會釋放鎖,并且需要 synchronized 配合,需要外部喚醒。

park

park 是 LockSupport 類的靜態方法,它的作用是暫停線程的執行,直到其他線程調用 unpark() 或線程被中斷,暫停期間不會釋放持有的鎖
它不需要配合 synchronized,也不會釋放鎖資源,unpark() 會提前提供一個許可證,下次 park 時不會進入阻塞。


九、多把鎖相關

多把鎖的優勢是可以增加并發度,但是如果一個線程需要多把鎖就容易發生死鎖,例如哲學家就餐問題。死鎖屬于活躍性問題,除了死鎖還有活鎖和饑餓兩種情況。活鎖是兩個線程互為對方的結束條件而無法結束,饑餓則是一個線程的優先級太低,始終無法得到CPU的調度,拿不到鎖。


十、ReentrantLock

可重入是指同一個線程如果首次獲得了這把鎖,那么因為它是這把鎖的擁有者,因此有權利再次獲取這把鎖。如果是不可重入鎖,那么第二次獲得鎖時,自己也會被鎖擋住。

  1. 可重入鎖的等待期間可以被 interrupt 打斷。
  2. 獲取鎖超時會立即失敗。
  3. ReentrantLock 默認是不公平的,也支持公平模式。
  4. ReentrantLock 支持多個條件變量(多間休息室)。
  5. 內部維護持有計數,記錄鎖被同一線程獲取的次數,確保完全釋放。

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

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

相關文章

網絡安全ctf試題 ctf網絡安全大賽真題

MISC 1 簽到 難度 簽到 復制給出的flag輸入即可 2 range_download 難度 中等 flag{6095B134-5437-4B21-BE52-EDC46A276297} 0x01 分析dns流量,發現dns && ip.addr1.1.1.1存在dns隧道數據,整理后得到base64: cGFzc3dvcmQ6IG5zc195eWRzIQ 解…

centos7操作系統下安裝docker,及查看docker進程是否啟動

centos7下安裝docker,需要用到的yun命令 (yum命令用于添加卸載程序) 1.設置倉庫: yum-config-manager \--add-repo \http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 2.安裝 Docker Engine-Community yum in…

私有云基礎架構與運維(二)

二.私有云基礎架構 【項目概述】 經過云計算基礎知識及核心技術的學習后,希望進一步了解 IT 基礎架構的演變過 程,通過學習傳統架構、集群架構以及私有云基礎架構的相關知識,認識企業從傳統 IT 基 礎架構到私有云基礎架構轉型的必要性。…

Linux 系統不同分類的操作命令區別

Linux 系統有多種發行版,每種發行版都有其獨特的操作命令和工具。以下是一些常見的分類及其操作命令的區別: 1. 基于 Red Hat 的發行版 (RHEL, CentOS, Fedora) 1.1 包管理 安裝軟件包: bash復制 sudo yum install <package> 更新軟件包: bash復制 sudo yum update…

?PLC數據類型和?C#數據類型的數據類型映射表

數據類型映射表 ?PLC數據類型?C#數據類型讀取方式?補充說明BitboolDBX布爾值BytebyteDBB單字節無符號整數WordushortDBW16位無符號整數DWorduintDBD32位無符號整數Intshort16位有符號整數DIntint32位有符號整數RealfloatDBR單精度浮點數LRealdoubleDBL雙精度浮點數Stringstr…

windows部署spleeter 版本2.4.0:分離音頻的人聲和背景音樂

windows部署spleeter 版本2.4.0&#xff1a;分離音頻的人聲和背景音樂 一、Spleeter 是什么&#xff1f; Spleeter 是由法國音樂流媒體公司 Deezer 開發并開源的一款基于深度學習的音頻分離工具。它能夠將音樂中的不同音軌&#xff08;如人聲、鼓、貝斯、鋼琴等&#xff09;分…

QTS單元測試框架

1.QTS單元測試框架介紹 目前QTS項目采用C/C語言,而CppUnit就是xUnit家族中的一員,它是一個專門面向C的單元測試框架。因此,QTS采用CppUnit測試框架是比較理想的選擇。 CppUnit按照層次來管理測試,最底層的就是TestCase,當有了幾個TestCase以后&#xff0c;可以將它們組織成Te…

dify + ollama + deepseek-r1+ stable-diffusion 構建繪畫智能體

故事背景 stable-diffusion 集成進 dify 后&#xff0c;我們搭建一個小智能體&#xff0c;驗證下文生圖功能 業務流程 #mermaid-svg-6nSwwp69eMizP6bt {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6nSwwp69eMiz…

分享幾個論文校對相關的deepseek提示詞

論文校對 1.檢查這段文字是否有語法或風格錯誤&#xff1a;[在此處粘貼您的文本]。 2.審查我的[文件類型&#xff0c;例如&#xff0c;“論文”]中的這一段落是否有語法或風格錯誤&#xff1a;[在此處粘貼您的文本]。 3.請審查我關于[具體主題&#xff0c;例如&#xff0c;…

【極光 Orbit?STC8A-8H】02. STC8 單片機工程模板創建

【極光 Orbit?STC8A-8H】02. STC8 單片機工程模板創建 七律 單片機 小小芯片大乾坤&#xff0c;集成世界在其中。 初學雖感千重難&#xff0c;實踐方知奧妙通。 今天的講法和過去不同&#xff0c;直接來一個多文件模塊化的工程模板創建&#xff0c;萬事開頭難&#xff0c;…

mac安裝nvm=>node=>nrm

下載并安裝 NVM 運行以下命令下載并安裝 NVM&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.4/install.sh | bash 配置環境變量 vim ~/.zshrc 按 i 將如下代碼復制進去&#xff0c;controlc &#xff0c;再按 :wq完成編輯 export NVM_DIR…

K8S學習之基礎十一:k8s中容器鉤子

容器鉤子 容器鉤子分為post-start和pre-stop post-start&#xff1a;容器啟動后執行的命令 pre-stop&#xff1a;容器關閉前執行的命令&#xff0c;可用于優雅關閉 # 分別定義兩個鉤子&#xff0c;啟動pod后更新index.html&#xff0c;關閉pod前正常關閉服務 vi post-pre.…

K8s 1.27.1 實戰系列(三)安裝網絡插件

Kubernetes 的網絡插件常見的有 Flannel 和 Calico ,這是兩種主流的 CNI(容器網絡接口)解決方案,它們在設計理念、實現方式、性能特征及適用場景上有顯著差異。以下是兩者的綜合對比分析: 一、Flannel 和 Calico 1. 技術基礎與網絡實現 Flannel 核心機制:基于 Overlay …

【五.LangChain技術與應用】【24.LangChain RAG文本向量化與存儲:智能檢索的核心技術】

凌晨三點,北京中關村的某棟辦公樓依然燈火通明。28歲的算法工程師小李盯著屏幕上的代碼,突然拍案而起:"終于成了!"他開發的智能客服系統在連續失敗78次后,首次準確識別出用戶"我想換個能打游戲的便宜手機"的真實需求——需要兼顧游戲性能和價格的機型…

深度學習五大模型:CNN、Transformer、BERT、RNN、GAN詳細解析

卷積神經網絡&#xff08;Convolutional Neural Network, CNN&#xff09; 原理 &#xff1a;CNN主要由卷積層、池化層和全連接層組成。卷積層通過卷積核在輸入數據上進行卷積運算&#xff0c;提取局部特征&#xff1b;池化層則對特征圖進行下采樣&#xff0c;降低特征維度&…

特征分解(Eigen decomposition)在深度學習中的應用與理解

特征分解在深度學習中的應用與理解 特征分解&#xff08;Eigendecomposition&#xff09;是線性代數中的一個核心工具&#xff0c;在深度學習領域有著廣泛的應用&#xff0c;尤其是在涉及矩陣操作和概率模型時。對于研究者來說&#xff0c;理解特征分解不僅有助于掌握數學基礎…

分布式ID生成方案:數據庫號段、Redis與第三方開源實現

分布式ID生成方案&#xff1a;數據庫號段、Redis與第三方開源實現 引言 在分布式系統中&#xff0c;全局唯一ID生成是核心基礎能力之一。本文針對三種主流分布式ID生成方案&#xff08;數據庫號段模式、Redis方案、第三方開源框架&#xff09;進行解析&#xff0c;從實現原理…

rabbitmq-amqp事務消息+消費失敗重試機制+prefetch限流

1. 安裝和配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <arti…

【Python】05、Python運算符

文章目錄 1.算術運算符2.賦值運算符3.關系運算符4.邏輯運算符4.1 布爾值邏輯運算4.2 非布爾值的邏輯運算符 5.條件運算符6.運算符優先級 運算符也稱為操作符&#xff0c;可以對一個或多個值進行運算或各種操作。比如、-、都屬于運算符 1.算術運算符 加法 如果是兩個字符串之間…

2025-03-06 學習記錄--C/C++-PTA 習題6-6 使用函數輸出一個整數的逆序數

合抱之木&#xff0c;生于毫末&#xff1b;九層之臺&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、題目描述 ?? 二、代碼&#xff08;C語言&#xff09;?? #include <stdio.h>int reverse( int number );int main…