Java - Synchronized的鎖升級之路

Synchronized鎖

Synchronized在Java JVM里的實現是基于進入和退出Monitor對象來實現方法同步和代碼塊同步的

monitor enter指令是在編譯后插入到同步代碼塊的開始位置

而monitor exit是插入到方法結束處和異常處

JVM要保證每個monitor enter必須有對應的monitor exit與之配對。

任何對象都有一個monitor與之關聯,當且一個monitor被持有后,它將處于鎖定狀態。線程執行到monitor enter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖。

synchronized用的鎖是存在Java對象頭里的。如果對象是數組類型,則虛擬機用3個字寬(Word)存儲對象頭,如果對象是非數組類型,則用2字寬存儲對象頭。在32位虛擬機中,1字寬等于4字節,即32bit。數組類多一個字節用于存儲數組長度,也就是說程序獲取數組長度的時間復雜度為O(1)。

java對象頭的存儲結構

鎖狀態25bit4bit1bit是否是偏向鎖2bit 鎖標志位
無鎖狀態對象的hashCode對象分代年齡001

?在運行期間,Mark Word里存儲的數據會隨著鎖標志位的變化而變化。Mark Word可能變化為存儲以下4種數據:

Mark Word的狀態變化

鎖狀態25bit4bit1bit2bit
23bit2bit是否是偏向鎖鎖標志位
輕量級鎖指向棧中鎖記錄的指針00
重量級鎖指向互斥量(重量級鎖)的指針10
GC標記11
偏向鎖線程IDEpoch對象分代年齡101


鎖的升級

Java 1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”

鎖一共有4種狀態,級別從低到高依次是:無鎖狀態偏向鎖狀態輕量級鎖狀態重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。


偏向鎖

在鎖不存在多線程競爭情況下,為了減小線程獲取鎖的代價而引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程再進入同步塊時只需簡單判斷下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。

  1. 如果本身是無鎖狀態(初始狀態),只需CAS設置偏向鎖指向自己即可;
  2. 判斷當前對象是否是偏向鎖,判斷擁有該偏向鎖的線程是否還存在(擁有偏向鎖的線程使用完畢后不會主動釋放),不存在時直接CAS設置偏向鎖指向自己線程;
  3. 如果擁有該偏向鎖的線程還存在,則會暫停擁有偏向鎖的線程,這一步操作是在全局安全點進行的。設置鎖標志位為00,偏向鎖標志位為0,從擁有偏向鎖線程A的空閑monitor record中讀取一條,放至線程A的當前monitor record中,然后更新mark word,將mark word指向線程A中monitor record的指針,這樣就完成了偏向鎖升級輕量級鎖。之后持有鎖的線程會繼續執行,競爭該輕量級鎖的線程自旋獲取該對象。
注意:輕量級鎖的獲取釋放需要多次CAS操作,而偏向鎖只是在置換ThreadID時進行一次CAS操作。
偏向鎖獲取后線程不會主動釋放,偏向鎖只有在其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖(被動釋放,此時會發生鎖升級)。
偏向鎖在JDK 6及以后的JVM里是默認啟用的。可以通過JVM參數關閉偏向鎖: -XX:-UseBiasedLocking=false,關閉之后程序默認會進入輕量級鎖狀態。
偏向鎖的撤銷需要在全局安全點上進行,它會暫停所有持有偏向鎖的線程,判斷鎖對象是否處于鎖定狀態。

可以發現偏向鎖適用于從始至終都只有一個線程在運行的情況,省略掉了自旋獲取鎖,以及重量級鎖互斥的開銷,這種鎖的開銷最低,性能最好接近于無鎖狀態,但是如果線程之間存在競爭的話,就需要頻繁的去暫停擁有偏向鎖的線程然后檢查狀態,決定是否重新偏向還是升級為輕量級別鎖,性能就會大打折扣了,如果事先能夠知道可能會存在競爭那么可以選擇關掉偏向鎖。


輕量級鎖

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

輕量級鎖在加鎖失敗進行CAS達到一定次數后(自旋鎖默認的次數為 10 次可以通過 -XX:PreBlockSpin 來更改),就會升級為重量級鎖;在解鎖失敗,鎖也會升級為重量級鎖。
一旦鎖升級成重量級鎖(就不會再恢復到輕量級鎖狀態),當鎖處于這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

輕量級鎖什么時候會解鎖失敗呢?在發生鎖競爭時并且占用鎖的線程未釋放,這時(自旋默認了10次還是未獲取到鎖)競爭鎖的線程就會 將Mark Word 修改為重量級鎖,并且將自己阻塞在該鎖的monitor對象上。之后占用鎖的線程將棧幀中的 Mark Word進行CAS替換回對象頭的 Mark Word 的時候,發現有其它線程競爭該鎖(已經由競爭鎖的線程更改了鎖狀態),然后它釋放鎖并且喚醒在等待的線程,后續的線程操作就全部都是重量級鎖了。


重量級鎖

重量級鎖也就是普通的悲觀鎖了,也就是競爭鎖失敗會阻塞等待喚醒再次競爭那種,關于這幾種鎖的對比如下:

優 點缺 點適用場景
偏向鎖加鎖和解鎖不需要額外的消耗,和執
行非同步方法相比僅存在納秒級的差距
如果線程間存在鎖競爭,
會帶來額外的鎖撤銷的消耗
適用于只有一個線程訪
問同步塊場景
輕量級鎖競爭的線程不會阻塞,提高了程序的
響應速度
如果始終得不到鎖競爭的
線程,使用自旋會消耗CPU
追求響應時間
同步塊執行速度非常快
重量級鎖線程競爭不使用自旋,不會消耗CPU線程阻塞,響應時間緩慢

追求吞吐量

同步塊執行速度較長

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

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

相關文章

解決服務端渲染程序SSR運行時報錯: ReferenceError: document is not defined

現象: 原因: 該錯誤表明在服務端渲染 (SSR) 過程中,有一些代碼嘗試在沒有瀏覽器環境的情況下執行與瀏覽器相關的操作。這在服務端渲染期間是一個常見的問題,因為在服務端渲染期間是沒有瀏覽器 API。 解決辦法: 1. 修…

bat腳本之while

在批處理(BAT)腳本中,while循環是一種常用的控制流結構,用于在滿足特定條件的情況下重復執行一段代碼。 while循環的基本語法如下: while [ condition ] do command1 command2 ... commandN done這里的 cond…

【2023傳智杯-新增場次】第六屆傳智杯程序設計挑戰賽AB組-DEF題復盤解題分析詳解【JavaPythonC++解題筆記】

本文僅為【2023傳智杯-第二場】第六屆傳智杯程序設計挑戰賽-題目解題分析詳解的解題個人筆記,個人解題分析記錄。 本文包含:第六屆傳智杯程序設計挑戰賽題目、解題思路分析、解題代碼、解題代碼詳解 文章目錄 一.前言二.賽題題目D題題目-E題題目-F題題目-二.賽題題解D題題解-…

深入理解Sentinel系列-1.初識Sentinel

👏作者簡介:大家好,我是愛吃芝士的土豆倪,24屆校招生Java選手,很高興認識大家📕系列專欄:Spring源碼、JUC源碼、Kafka原理、分布式技術原理🔥如果感覺博主的文章還不錯的話&#xff…

待做-待補充-每個節點做事,時間,以及與角度的關系

文章目錄 待定內容紅黑樹應用場景限制什么是二叉樹遍歷遞歸遍歷1.前序遍歷 進入節點時2.中序遍歷 遍歷完左子樹回到節點。此操作需要等到所有左樹節點做完后才會做3.后序遍歷 遍歷完左右子樹回到節點。左右子樹的所有節點都做完操作后,回到當前節點才會做此操作 …

如何搭建自己的直播電商系統?

當下,傳統的圖文電商模式已經走向沒落,視頻電商備受追捧。抖音、快手、小紅書、京東、淘寶、拼多多都在發力直播電商業務,尤其是以抖音為首的直播電商備受用戶歡迎,它具有實時直播和強互動的特點,是傳統電商所不具備的…

<HarmonyOS第一課>保存應用數據【課后考核】

【習題】保存應用數據 判斷題 首選項是關系型數據庫。 錯誤(False) 應用中涉及到Student信息,如包含姓名,性別,年齡,身高等信息可以用首選項來存儲。 錯誤(False) 同一應用或進程中每個文件僅存在一個Preferences實例。 正確(T…

最長子串問題(LCS)--動態規劃解法

題目描述: 如果Z既是X的子串,又是Y的子串,則稱Z為X和Y的公共子串。 如果給定X、Y,求出最長Z及其長度。 注意:這里求的不是子序列,兩者的意思并不相同。子串要求連續,子序列并不需要。 如果想…

simulinkveristandlabview聯合仿真環境搭建

目錄 開篇廢話 軟件版本 明確需求 軟件安裝 matlab2020a veristand2020 R4 VS2017 VS2010 軟件安裝驗證 軟件資源分享 開篇廢話 推免之后接到的第一個讓人難繃的活,網上開源的軟件資料和成功的案例很少,查來查去就那么幾篇,而且版本…

SpringData

1.為什么要學習SpringData? 是因為對數據存儲的框架太多了,全部都要學習成本比較高,SpringData對這些數據存儲層做了一個統一,學習成本大大降低。

SQL命令---修改字段的數據類型

介紹 使用sql語句修改字段的數據類型。 命令 alter table 表明 modify 字段名 數據類型;例子 有一張a表,表里有一個id字段,長度為11。使用命令將長度修改為12 下面使用命令進行修改: alter table a modify id int(12) NOT NULL;下面使修…

stm32使用多串口不輸出無反應的問題(usart1、usart2)

在使用stm32c8t6單片機時,由于需要使用兩個串口usart1 、usart2。usart1用作程序燒錄、調試作用,串口2用于與其它模塊進行通信。 使用串口1時,正常工作,使用串口2時,無反應。查閱了相關資料串口2在PA2\PA3 引腳上。RX…

[僅供學習,禁止用于違法]編寫一個程序來手動設置Windows的全局代理開或關,實現對所有網絡請求攔截和數據包捕獲(抓包或VPN的應用)

文章目錄 介紹一、實現原理二、通過注冊表設置代理2.1 開啟代理2.2 關閉代理2.3 添加代理地址2.4 刪除代理設置信息 三、代碼實戰3.1 程序控制代理操作控制3.1.1 開啟全局代理3.1.2 添加代理地址3.1.3 關閉代理開關3.1.4 刪除代理信息 3.2 攔截所有請求 介紹 有一天突發奇想&am…

在git使用SSH密鑰進行github身份認證學習筆記

1.生成ssh密鑰對 官網文檔:Https://docs.github.com/zh/authentication(本節內容對應的官方文檔,不清晰的地方可參考此內容) 首先,啟動我們的git bush(在桌面右鍵,點擊 Git Bush Here &#xf…

iOS_制作 cocopods庫

文章目錄 1.創建項目2.配置項目3.發布 1.創建項目 在 github 上創建倉庫&#xff0c;克隆到本地&#xff1a; git clone https://github.com/mxh-mo/MOOXXX.git在項目目錄下執行&#xff1a; pod lib create <庫名稱>進行一些配置的選擇&#xff1a; # 希望在那個平臺…

隨機分詞與tokenizer(BPE->BBPE->Wordpiece->Unigram->sentencepiece->bytepiece)

0 tokenizer綜述 根據不同的切分粒度可以把tokenizer分為: 基于詞的切分&#xff0c;基于字的切分和基于subword的切分。 基于subword的切分是目前的主流切分方式。subword的切分包括: BPE(/BBPE), WordPiece 和 Unigram三種分詞模型。其中WordPiece可以認為是一種特殊的BPE。完…

實時最優控制(Real-Time Optimal Control)工具

系列文章目錄 前言 許多現代控制方法&#xff0c;如模型預測控制&#xff08;model-predictive control&#xff09;&#xff0c;在很大程度上依賴于實時解決優化問題。特別是&#xff0c;高效解決優化控制問題的能力使復雜機器人系統在實現高動態行為&#xff08;highly dyna…

求Sn=m+mm+mmm+...+mm..mmm(有n個m)的值

題目&#xff1a;求 的值 一、做這個題我們其實可以直接一個for求解&#xff1a; a,aa,aaa...我們很容易知道它們后一項與前一項的關系就是&#xff1b; public static void Sum(int m,int n){long sum 0L;long curAn 0;for (int i 0; i < n; i){curAn m 10* curAn;/…

Qexo博客后臺管理部署

Qexo博客后臺管理部署 個人主頁 個人博客 參考文檔 https://www.oplog.cn/qexo/本地部署 采用本地Docker部署管理本地Hexo 下載代碼包 若無法下載使用科學工具下載到本地在上傳到服務器 wget https://github.com/Qexo/Qexo/archive/refs/tags/3.0.1.zip# 解壓 unzip Qexo…

C++中的前綴和

C中的前綴和&#xff08;Prefix Sum&#xff09;是一種優化算法&#xff0c;用于計算原數組中每個元素前綴和&#xff08;前面所有元素的累加和&#xff09;&#xff0c;可以在O(n)時間內實現。 #include<iostream> using namespace std;const int MAXN 100010;int Pre…