17張圖看穿synchronized關(guān)鍵字
引子
小艾和小牛在路上相遇,小艾一臉沮喪。
小牛:小艾小艾,發(fā)生甚么事了?
小艾:別提了,昨天有個(gè)面試官問了我好幾個(gè)關(guān)于 synchronized
關(guān)鍵字的問題,沒答上來。
小艾:我后來查了很多資料,有二十多頁的概念說明,也有三十來頁的源碼剖析,看得我頭大。
小牛:你那看的是死知識(shí),不好用,你得聽我的總結(jié)。
小艾:看來是有備而來,那您給講講吧。
小牛:那咱們開始!
synchronized關(guān)鍵字引入
我們知道,在多線程程序中往往會(huì)出現(xiàn)這么一個(gè)情況:多個(gè)線程同時(shí)訪問某個(gè)線程間的共享變量。來舉個(gè)例子吧:
假設(shè)銀行存款業(yè)務(wù)寫了兩個(gè)方法,一個(gè)是存錢 store()
方法 ,一個(gè)是查詢余額 get()
方法。假設(shè)初始客戶小明的賬戶余額為 0 元。(PS:這個(gè)例子只是個(gè) toy demo
,為了方便大家理解寫的,真實(shí)的業(yè)務(wù)場景不會(huì)這樣。)
????//?account?客戶在銀行的存款?
????public?void?store(int?money){
????????int?newAccount=account+money;
????????account=newAccount;
????}
????public?void?get(){
????????System.out.print("小明的銀行賬戶余額:");
????????System.out.print(account);
????}
如果小明為自己存款 1 元,我們期望的線程調(diào)用情況如下:
首先會(huì)啟動(dòng)一個(gè)線程調(diào)用
store()
方法,為客戶賬戶余額增加 1;再啟動(dòng)一個(gè)線程調(diào)用
get()
方法,輸出客戶的新余額為 1。
但實(shí)際情況可能由于線程執(zhí)行的先后順序,出現(xiàn)如圖所示的錯(cuò)誤:
小明會(huì)驚奇的以為自己的錢沒存上。這就是一個(gè)典型的由共享數(shù)據(jù)引發(fā)的并發(fā)數(shù)據(jù)沖突問題。
解決方式也很簡單,讓并發(fā)執(zhí)行會(huì)產(chǎn)生問題的代碼段不并發(fā)行了。
如果 store()
方法 執(zhí)行完,才能執(zhí)行 get()
方法,而不是像上圖一樣并發(fā)執(zhí)行,自然不會(huì)出現(xiàn)這個(gè)問題。那如何才能做到呢?
答案就是使用 synchronized
關(guān)鍵字。
我們先從直覺上思考一下,如果要實(shí)現(xiàn)先執(zhí)行 store()
方法,再執(zhí)行 get()
方法的話該怎么設(shè)計(jì)。
我們可以設(shè)置某個(gè)鎖,鎖會(huì)有兩種狀態(tài),分別是上鎖和解鎖。在 store()
方法執(zhí)行之前,先觀察這個(gè)鎖的狀態(tài),如果是上鎖狀態(tài),就進(jìn)入阻塞,代碼不運(yùn)行;
如果這把鎖是解鎖狀態(tài),那就先將這把鎖狀態(tài)變?yōu)樯湘i,之后接著運(yùn)行自己的代碼。運(yùn)行完成之后再將鎖狀態(tài)設(shè)置為解鎖。
對(duì)于 get()
方法也是如此。
Java 中的 synchronized
關(guān)鍵字就是基于這種思想設(shè)計(jì)的。在 synchronized
關(guān)鍵字中,鎖就是一個(gè)對(duì)象。
synchronized
一共有三種使用方法:
直接修飾某個(gè)實(shí)例方法。像上文代碼一樣,在這種情況下多線程并發(fā)訪問實(shí)例方法時(shí),如果其他線程調(diào)用同一個(gè)對(duì)象的被
synchronized
修飾的方法,就會(huì)被阻塞。相當(dāng)于把鎖記錄在這個(gè)方法對(duì)應(yīng)的對(duì)象上。
????//?account?客戶在銀行的存款?
????public?synchronized?void?store(int?money){
????????int?newAccount=account+money;
????????account=newAccount;
????}
????public?synchronized?void?get(){
????????System.out.print("小明的銀行賬戶余額:");
????????System.out.print(account);
????}
直接修飾某個(gè)靜態(tài)方法。在這種情況下進(jìn)行多線程并發(fā)訪問時(shí),如果其他線程也是調(diào)用屬于同一類的被
synchronized
修飾的靜態(tài)方法,就會(huì)被阻塞。相當(dāng)于把鎖信息記錄在這個(gè)方法對(duì)應(yīng)的類上。
????public?synchronized?static?void?get(){
????????···
????}
修飾代碼塊。如果此時(shí)有別的線程也想訪問某個(gè)被
synchronized(對(duì)象0)
修飾的同步代碼塊時(shí),也會(huì)被阻塞。
????public?static?void?get(){
????????synchronized(對(duì)象0){
????????????···
????????}
????}
小艾問:我看了不少參考書還有網(wǎng)上資料,都說 synchronized
的鎖是鎖在對(duì)象上的。關(guān)于這句話,你能深入講講嗎?
小?;卮鸬溃簞e急,我先講講 Java 對(duì)象在內(nèi)存中的表示。
Java 對(duì)象在內(nèi)存中的表示
講清 synchronized
關(guān)鍵字的原理前需要理清 Java 對(duì)象在內(nèi)存中的表示方法。
上圖就是一個(gè) Java 對(duì)象在內(nèi)存中的表示。我們可以看到,內(nèi)存中的對(duì)象一般由三部分組成,分別是對(duì)象頭、對(duì)象實(shí)際數(shù)據(jù)和對(duì)齊填充。
對(duì)象頭包含 Mark Word、Class Pointer和 Length 三部分。
Mark Word 記錄了對(duì)象關(guān)于鎖的信息,垃圾回收信息等。
Class Pointer 用于指向?qū)ο髮?duì)應(yīng)的 Class 對(duì)象(其對(duì)應(yīng)的元數(shù)據(jù)對(duì)象)的內(nèi)存地址。
Length只適用于對(duì)象是數(shù)組時(shí),它保存了該數(shù)組的長度信息。
對(duì)象實(shí)際數(shù)據(jù)包括了對(duì)象的所有成員變量,其大小由各個(gè)成員變量的大小決定。
對(duì)齊填充表示最后一部分的填充字節(jié)位,這部分不包含有用信息。
我們剛才講的鎖 synchronized
鎖使用的就是對(duì)象頭的 Mark Word 字段中的一部分。
Mark Word 中的某些字段發(fā)生變化,就可以代表鎖不同的狀態(tài)。
由于鎖的信息是記錄在對(duì)象里的,有的開發(fā)者也往往會(huì)說鎖住對(duì)象這種表述。
無鎖狀態(tài)的 Mark Word
這里我們以無鎖狀態(tài)的 Mark Word 字段舉例:
如果當(dāng)前對(duì)象是無鎖狀態(tài),對(duì)象的 Mark Word 如圖所示。
我們可以看到,該對(duì)象頭的 Mark Word 字段分為四個(gè)部分:
對(duì)象的 hashCode ;
對(duì)象的分代年齡,這部分用于對(duì)對(duì)象的垃圾回收;
是否為偏向鎖位,1代表是,0代表不是;
鎖標(biāo)志位,這里是 01。
synchronized關(guān)鍵字的實(shí)現(xiàn)原理
講完了 Java 對(duì)象在內(nèi)存中的表示,我們下一步來講講 synchronized
關(guān)鍵字的實(shí)現(xiàn)原理。
從前文中我們可以看到, synchronized
關(guān)鍵字有兩種修飾方法
直接作為關(guān)鍵字修飾在方法上,將整個(gè)方法作為同步代碼塊:
????public?synchronized?static?void?`get()`{
????????···
????}
修飾在同步代碼塊上。
????public?static?void?`get()`{
????????synchronized(對(duì)象0){
????????????···
????????}
????}
針對(duì)這兩種情況,Java 編譯時(shí)的處理方法并不相同。
對(duì)于第一種情況,編譯器會(huì)為其自動(dòng)生成了一個(gè) ACC_SYNCHRONIZED
關(guān)鍵字用來標(biāo)識(shí)。
在 JVM 進(jìn)行方法調(diào)用時(shí),當(dāng)發(fā)現(xiàn)調(diào)用的方法被 ACC_SYNCHRONIZED
修飾,則會(huì)先嘗試獲得鎖。
對(duì)于第二種情況,編譯時(shí)在代碼塊開始前生成對(duì)應(yīng)的1個(gè) monitorenter
指令,代表同步塊進(jìn)入。2個(gè) monitorexit
指令,代表同步塊退出。
這兩種方法底層都需要一個(gè) reference 類型的參數(shù),指明要鎖定和解鎖的對(duì)象。
如果 synchronized
明確指定了對(duì)象參數(shù),那就是該對(duì)象。
如果沒有明確指定,那就根據(jù)修飾的方法是實(shí)例方法還是類方法,取對(duì)應(yīng)的對(duì)象實(shí)例或類對(duì)象(Java 中類也是一種特殊的對(duì)象)作為鎖對(duì)象。
每個(gè)對(duì)象維護(hù)著一個(gè)記錄著被鎖次數(shù)的計(jì)數(shù)器。當(dāng)一個(gè)線程執(zhí)行 monitorenter
,該計(jì)數(shù)器自增從 0 變?yōu)?1;
當(dāng)一個(gè)線程執(zhí)行 monitorexit
,計(jì)數(shù)器再自減。當(dāng)計(jì)數(shù)器為 0 的時(shí)候,說明對(duì)象的鎖已經(jīng)釋放。
小艾問:為什么會(huì)有兩個(gè) monitorexit
指令呢?
小牛答:正常退出,得用一個(gè) monitorexit
吧,如果中間出現(xiàn)異常,鎖會(huì)一直無法釋放。所以編譯器會(huì)為同步代碼塊添加了一個(gè)隱式的 try-finally
異常處理,在 finally
中會(huì)調(diào)用 monitorexit
命令最終釋放鎖。
重量級(jí)鎖
小艾問:那么問題來了,之前你說鎖的信息是記錄在對(duì)象的 Mark Word 中的,那現(xiàn)在冒出來的 monitor
又是什么呢?
小牛答:我們先來看一下重量級(jí)鎖對(duì)應(yīng)對(duì)象的 Mark Word。
在 Java 的早期版本中,synchronized
鎖屬于重量級(jí)鎖,此時(shí)對(duì)象的 Mark Word 如圖所示。
我們可以看到,該對(duì)象頭的 Mark Word 分為兩個(gè)部分。第一部分是指向重量級(jí)鎖的指針,第二部分是鎖標(biāo)記位。
而這里所說的指向重量級(jí)鎖的指針就是 monitor
。
英文詞典翻譯 monitor
是監(jiān)視器。Java 中每個(gè)對(duì)象會(huì)對(duì)應(yīng)一個(gè)監(jiān)視器。
這個(gè)監(jiān)視器其實(shí)也就是監(jiān)控鎖有沒有釋放,釋放的話會(huì)通知下一個(gè)等待鎖的線程去獲取。
monitor
的成員變量比較多,我們可以這樣理解:
我們可以將 monitor
簡單理解成兩部分,第一部分表示當(dāng)前占用鎖的線程,第二部分是等待這把鎖的線程隊(duì)列。
如果當(dāng)前占用鎖的線程把鎖釋放了,那就需要在線程隊(duì)列中喚醒下一個(gè)等待鎖的線程。
但是阻塞或喚醒一個(gè)線程需要依賴底層的操作系統(tǒng)來實(shí)現(xiàn),Java 的線程是映射到操作系統(tǒng)的原生線程之上的。
而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)轉(zhuǎn)換需要花費(fèi)很多的處理器時(shí)間,甚至可能比用戶代碼執(zhí)行的時(shí)間還要長。
由于這種效率太低,Java 后期做了改進(jìn),我再來詳細(xì)講一講。
CAS算法
在講其他改進(jìn)之前,我們先來聊聊 CAS 算法。CAS 算法全稱為 Compare And Swap。
顧名思義,該算法涉及到了兩個(gè)操作,比較(Compare)和交換(Swap)。
怎么理解這個(gè)操作呢?我們來看下圖:
我們知道,在對(duì)共享變量進(jìn)行多線程操作的時(shí)候,難免會(huì)出現(xiàn)線程安全問題。
對(duì)該問題的一種解決策略就是對(duì)該變量加鎖,保證該變量在某個(gè)時(shí)間段只能被一個(gè)線程操作。
但是這種方式的系統(tǒng)開銷比較大。因此開發(fā)人員提出了一種新的算法,就是大名鼎鼎的 CAS 算法。
CAS 算法的思路如下:
該算法認(rèn)為線程之間對(duì)變量的操作進(jìn)行競爭的情況比較少。
算法的核心是對(duì)當(dāng)前讀取變量值
E
和內(nèi)存中的變量舊值V
進(jìn)行比較。如果相等,就代表其他線程沒有對(duì)該變量進(jìn)行修改,就將變量值更新為新值
N
。如果不等,就認(rèn)為在讀取值
E
到比較階段,有其他線程對(duì)變量進(jìn)行過修改,不進(jìn)行任何操作。
當(dāng)線程運(yùn)行 CAS 算法時(shí),該運(yùn)行過程是原子操作,原子操作的含義就是線程開始跑這個(gè)函數(shù)后,運(yùn)行過程中不會(huì)被別的程序打斷。
我們來看看實(shí)際上 Java 語言中如何使用這個(gè) CAS 算法,這里我們以 AtomicInteger
類中的 compareAndSwapInt()
方法舉例:
public?final?native?boolean?compareAndSwapInt
(Object?var1,?long?var2,?int?var3,?int?var4)
可以看到,該函數(shù)原型接受四個(gè)參數(shù):
第一個(gè)參數(shù)是一個(gè)
AtomicInteger
對(duì)象。第二個(gè)參數(shù)是該
AtomicInteger
對(duì)象對(duì)應(yīng)的成員變量在內(nèi)存中的地址。第三個(gè)參數(shù)是上圖中說的線程之前讀取的值
P
。第四個(gè)參數(shù)是上圖中說的線程計(jì)算的新值
V
。
偏向鎖
JDK 1.6 中提出了偏向鎖的概念。該鎖提出的原因是,開發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競爭,一把鎖往往是由同一個(gè)線程獲得的。
如果是這種情況,不斷的加鎖解鎖是沒有必要的。
那么能不能讓 JVM 直接負(fù)責(zé)在這種情況下加解鎖的事情,不讓操作系統(tǒng)插手呢?
因此開發(fā)者設(shè)計(jì)了偏向鎖。偏向鎖在獲取資源的時(shí)候,會(huì)在資源對(duì)象上記錄該對(duì)象是否偏向該線程。
偏向鎖并不會(huì)主動(dòng)釋放,這樣每次偏向鎖進(jìn)入的時(shí)候都會(huì)判斷該資源是否是偏向自己的,如果是偏向自己的則不需要進(jìn)行額外的操作,直接可以進(jìn)入同步操作。
下圖表示偏向鎖的 Mark Word結(jié)構(gòu):
可以看到,偏向鎖對(duì)應(yīng)的 Mark Word 包含該偏向鎖對(duì)應(yīng)的線程 ID、偏向鎖的時(shí)間戳和對(duì)象分代年齡。
偏向鎖的申請(qǐng)流程
我們再來看一下偏向鎖的申請(qǐng)流程:
首先需要判斷對(duì)象的 Mark Word 是否屬于偏向模式,如果不屬于,那就進(jìn)入輕量級(jí)鎖判斷邏輯。否則繼續(xù)下一步判斷;
判斷目前請(qǐng)求鎖的線程 ID 是否和偏向鎖本身記錄的線程 ID 一致。如果一致,繼續(xù)下一步的判斷,如果不一致,跳轉(zhuǎn)到步驟4;
判斷是否需要重偏向,重偏向邏輯在后面一節(jié)批量重偏向和批量撤銷會(huì)說明。如果不用的話,直接獲得偏向鎖;
利用 CAS 算法將對(duì)象的 Mark Word 進(jìn)行更改,使線程 ID 部分換成本線程 ID。如果更換成功,則重偏向完成,獲得偏向鎖。如果失敗,則說明有多線程競爭,升級(jí)為輕量級(jí)鎖。
值得注意的是,在執(zhí)行完同步代碼后,線程不會(huì)主動(dòng)去修改對(duì)象的 Mark Word,讓它重回?zé)o鎖狀態(tài)。
所以一般執(zhí)行完 synchronized
語句后,如果是偏向鎖的狀態(tài)的話,線程對(duì)鎖的釋放操作可能是什么都不做。
匿名偏向鎖
在 JVM 開啟偏向鎖模式下,如果一個(gè)對(duì)象被新建,在四秒后,該對(duì)象的對(duì)象頭就會(huì)被置為偏向鎖。
一般來說,當(dāng)一個(gè)線程獲取了一把偏向鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里不僅說明目前是偏向鎖狀態(tài),也會(huì)存儲(chǔ)鎖偏向的線程 ID。
在 JVM 四秒自動(dòng)創(chuàng)建偏向鎖的情況下,線程 ID 為0。
由于這種情況下的偏向鎖不是由某個(gè)線程求得生成的,這種情況下的偏向鎖也稱為匿名偏向鎖。
批量重偏向和批量撤銷
在生產(chǎn)者消費(fèi)者模式下,生產(chǎn)者線程負(fù)責(zé)對(duì)象的創(chuàng)建,消費(fèi)者線程負(fù)責(zé)對(duì)生產(chǎn)出來的對(duì)象進(jìn)行使用。
當(dāng)生產(chǎn)者線程創(chuàng)建了大量對(duì)象并執(zhí)行加偏向鎖的同步操作,消費(fèi)者對(duì)對(duì)象使用之后,會(huì)產(chǎn)生大量偏向鎖執(zhí)行和偏向鎖撤銷的問題。
Russell K和 Detlefs D在他們的文章提出了批量重偏向和批量撤銷的過程。
在上圖情景下,他們探討了能不能直接將偏向的線程換成消費(fèi)者的線程。
替換不是一件容易事,需要在 JVM 的眾多線程中找到類似上文情景的線程。
他們最后提出的解決方法是:
以類為單位,為每個(gè)類維護(hù)一個(gè)偏向鎖撤銷計(jì)數(shù)器,每一次該類的對(duì)象發(fā)生偏向撤銷操作時(shí),該計(jì)數(shù)器計(jì)數(shù) +1,當(dāng)這個(gè)計(jì)數(shù)值達(dá)到重偏向閾值時(shí),JVM 就認(rèn)為該類可能不適合正常邏輯,適合批量重偏向邏輯。這就是對(duì)應(yīng)上圖流程圖里的是否需要重偏向過程。
以生產(chǎn)者消費(fèi)者為例,生產(chǎn)者生產(chǎn)同一類型的對(duì)象給消費(fèi)者,然后消費(fèi)者對(duì)這些對(duì)象都需要執(zhí)行偏向鎖撤銷,當(dāng)撤銷過程過多時(shí)就會(huì)觸發(fā)上文規(guī)則,JVM 就注意到這個(gè)類了。
具體規(guī)則是:
每個(gè)類對(duì)象會(huì)有一個(gè)對(duì)應(yīng)的
epoch
字段,每個(gè)處于偏向鎖狀態(tài)對(duì)象的 Mark Word 中也有該字段,其初始值為創(chuàng)建該對(duì)象時(shí),類對(duì)象中的epoch
的值。每次發(fā)生批量重偏向時(shí),就將類對(duì)象的
epoch
字段 +1,得到新的值epoch_new
。遍歷 JVM 中所有線程的棧,找到該類對(duì)象,將其
epoch
字段改為新值。根據(jù)線程棧的信息判斷出該線程是否鎖定了該對(duì)象,將現(xiàn)在偏向鎖還在被使用的對(duì)象賦新值epoch_new
。下次有線程想獲得鎖時(shí),如果發(fā)現(xiàn)當(dāng)前對(duì)象的
epoch
值和類的epoch
不相等,不會(huì)執(zhí)行撤銷操作,而是直接通過 CAS 操作將其 Mark Word 的 Thread ID 改成當(dāng)前線程 ID。
批量撤銷相對(duì)于批量重偏向好理解得多,JVM 也會(huì)統(tǒng)計(jì)重偏向的次數(shù)。
假設(shè)該類計(jì)數(shù)器計(jì)數(shù)繼續(xù)增加,當(dāng)其達(dá)到批量撤銷的閾值后(默認(rèn)40),JVM 就認(rèn)為該類的使用場景存在多線程競爭,會(huì)標(biāo)記該類為不可偏向,之后對(duì)于該類的鎖升級(jí)為輕量級(jí)鎖。
輕量級(jí)鎖
輕量級(jí)鎖的設(shè)計(jì)初衷在于并發(fā)程序開發(fā)者的經(jīng)驗(yàn)“對(duì)于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競爭的”。
所以它的設(shè)計(jì)出發(fā)點(diǎn)也在線程競爭情況較少的情況下。我們先來看一下輕量級(jí)鎖的 Mark Word 布局。
如果當(dāng)前對(duì)象是輕量級(jí)鎖狀態(tài),對(duì)象的 Mark Word 如下圖所示。
我們可以看到,該對(duì)象頭Mark Word分為兩個(gè)部分。第一部分是指向棧中的鎖記錄的指針,第二部分是鎖標(biāo)記位,針對(duì)輕量級(jí)鎖該標(biāo)記位為 00。
小艾問:那這指向棧中的鎖記錄的指針是什么意思呢?
小牛答:這得結(jié)合輕量級(jí)鎖的上鎖步驟來慢慢講。
如果當(dāng)前這個(gè)對(duì)象的鎖標(biāo)志位為 01(即無鎖狀態(tài)或者輕量級(jí)鎖狀態(tài)),線程在執(zhí)行同步塊之前,JVM 會(huì)先在當(dāng)前的線程的棧幀中創(chuàng)建一個(gè) Lock Record,包括一個(gè)用于存儲(chǔ)對(duì)象頭中的 Mark Word 以及一個(gè)指向?qū)ο蟮闹羔槨?/p>
然后 JVM 會(huì)利用 CAS 算法對(duì)這個(gè)對(duì)象的 Mark Word 進(jìn)行修改。如果修改成功,那該線程就擁有了這個(gè)對(duì)象的鎖。我們來看一下如果上圖的線程執(zhí)行 CAS 算法成功的結(jié)果。
當(dāng)然 CAS 也會(huì)有失敗的情況。如果 CAS 失敗,那就說明同時(shí)執(zhí)行 CAS 操作的線程可不止一個(gè)了, Mark Word 也做了更改。
首先虛擬機(jī)會(huì)檢查對(duì)象的 Mark Word 字段指向棧中的鎖記錄的指針是否指向當(dāng)前線程的棧幀。如果是,那就說明可能出現(xiàn)了類似 synchronized
中套 synchronized
情況:
synchronized?(對(duì)象0)?{
????synchronized?(對(duì)象0)?{
????????···
????}
}
當(dāng)然這種情況下當(dāng)前線程已經(jīng)擁有這個(gè)對(duì)象的鎖,可以直接進(jìn)入同步代碼塊執(zhí)行。
否則說明鎖被其他線程搶占了,該鎖還需要升級(jí)為重量級(jí)鎖。
和偏向鎖不同的是,執(zhí)行完同步代碼塊后,需要執(zhí)行輕量級(jí)鎖的解鎖過程。解鎖過程如下:
通過 CAS 操作嘗試把線程棧幀中復(fù)制的 Mark Word 對(duì)象替換當(dāng)前對(duì)象的 Mark Word。
如果 CAS 算法成功,整個(gè)同步過程就完成了。
如果 CAS 算法失敗,則說明存在競爭,鎖升級(jí)為重量級(jí)鎖。
我們來總結(jié)一下輕量級(jí)鎖升級(jí)過程吧:
總結(jié)
這次我們了解了 synchronized
底層實(shí)現(xiàn)原理和對(duì)應(yīng)的鎖升級(jí)過程。最后我們再通過這張流程圖來回顧一下 synchronized
鎖升級(jí)過程吧。
巨人肩膀
實(shí)現(xiàn)Java虛擬機(jī):JVM故障診斷與性能優(yōu)化
深入理解java虛擬機(jī) JVM高級(jí)特性與最佳實(shí)踐
Russell K , Detlefs D . Eliminating synchronization-related atomic operations with biased locking and bulk rebiasing[C]// Acm Sigplan Conference on Object-oriented Programming Systems. ACM, 2006.
Dice D , Moir M S , Scherer Iii W N . Quickly reacquirable locks: US 2010.
https://github.com/farmerjohngit/myblog/issues/12
https://www.itqiankun.com/article/bias-lightweight-synchronized-lock
https://www.itqiankun.com/article/bias-lock-epoch-effect
https://www.hollischuang.com/archives/1883
http://www.ideabuffer.cn/2017/05/06/Java%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80/
http://www.ideabuffer.cn/2017/04/21/java-%E4%B8%AD%E7%9A%84%E9%94%81-%E5%81%8F%E5%90%91%E9%94%81%E3%80%81%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E3%80%81%E8%87%AA%E6%97%8B%E9%94%81%E3%80%81%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81/
https://blog.csdn.net/zhao_miao/article/details/84500771
推薦閱讀
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!